Compare commits

..
201 Commits
Author SHA1 Message Date
CaliBrain 2c6d6a02cd ci: debounce dev image builds instead of building nightly (#1376)
Replace the nightly cron and its check-changes job with a debounce.
Every push to main starts dev-image-debounce.yml, which waits out the
60-minute wait timer on the dev-image-debounce environment, then
dispatches the Docker workflow only if main still points at its commit.
A burst of merges now publishes one dev image, an hour after the last
merge.

The Docker workflow keeps only its tag and workflow_dispatch triggers,
so its history holds real builds only. The debounce workflow deletes its
own finished runs, so no-op runs don't pile up either.

Requires the dev-image-debounce environment with a 60-minute wait
timer (Settings → Environments).
2026-09-21 02:38:06 -04:00
dependabot[bot] 4a0675e0d3 build(deps): bump the python-deps group with 4 updates (#1375)
Bumps the python-deps group with 4 updates:
[gevent](https://github.com/gevent/gevent),
[emoji](https://github.com/carpedm20/emoji),
[seleniumbase](https://github.com/seleniumbase/SeleniumBase) and
[ruff](https://github.com/astral-sh/ruff).

Updates `gevent` from 26.8.0 to 26.9.0
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/gevent/gevent/commit/003c77a6f3da41c014dd415022fb61f9d91fc95d"><code>003c77a</code></a>
Preparing release 26.9.0</li>
<li><a
href="https://github.com/gevent/gevent/commit/725ecc3e53dd74f3828f1cf189e1cbeaebbdfae9"><code>725ecc3</code></a>
Merge pull request <a
href="https://redirect.github.com/gevent/gevent/issues/2209">#2209</a>
from bojanz/issue2207-resolve-result-on-setup-failure</li>
<li><a
href="https://github.com/gevent/gevent/commit/a5b68e812e8ddf65d13dfa117d8656fcb713d69b"><code>a5b68e8</code></a>
Adjust the ThreadPool after an unexpected worker exit</li>
<li><a
href="https://github.com/gevent/gevent/commit/67a725659bd622ac476bd39019209e11a1f18f2b"><code>67a7256</code></a>
Resolve the ThreadResult when worker task setup fails</li>
<li><a
href="https://github.com/gevent/gevent/commit/a3b307bb82cd9b875933a964e883015bf0ce18b2"><code>a3b307b</code></a>
Add change note for <a
href="https://redirect.github.com/gevent/gevent/issues/2211">#2211</a> /
<a
href="https://redirect.github.com/gevent/gevent/issues/2039">#2039</a>
[skip ci]</li>
<li><a
href="https://github.com/gevent/gevent/commit/b0ec8d3b4cb7cde5799dc57d35f74267e3b8259a"><code>b0ec8d3</code></a>
Merge pull request <a
href="https://redirect.github.com/gevent/gevent/issues/2211">#2211</a>
from afonsojanu/fix/subprocess-stdin-mode-missing-bi...</li>
<li><a
href="https://github.com/gevent/gevent/commit/29b00308c74a703e26178229f6edacf98a31a1cb"><code>29b0030</code></a>
Merge pull request <a
href="https://redirect.github.com/gevent/gevent/issues/2210">#2210</a>
from Shivakarthikeya23/issue-1946</li>
<li><a
href="https://github.com/gevent/gevent/commit/47ea41c7e2bc8ca50785a60e929e7335c75fc308"><code>47ea41c</code></a>
Merge pull request <a
href="https://redirect.github.com/gevent/gevent/issues/2208">#2208</a>
from bojanz/issue2206-skip-missing-hooks</li>
<li><a
href="https://github.com/gevent/gevent/commit/0ee0c838cf50255c40647109de5391f84630af6a"><code>0ee0c83</code></a>
Report the full binary mode string from FileObjectPosix</li>
<li><a
href="https://github.com/gevent/gevent/commit/3f3dc574d2b1e5381ea70bfea0da565fd4fcd6c8"><code>3f3dc57</code></a>
Fix AsyncResult accumulating traceback frames on repeated get()</li>
<li>Additional commits viewable in <a
href="https://github.com/gevent/gevent/compare/26.8.0...26.9.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `emoji` from 2.15.0 to 2.16.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/carpedm20/emoji/releases">emoji's
releases</a>.</em></p>
<blockquote>
<h2>v2.16.0</h2>
<ul>
<li>Update to Unicode 18.0</li>
<li>Update translations to CLDR release-49-alpha2</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/carpedm20/emoji/blob/master/CHANGES.md">emoji's
changelog</a>.</em></p>
<blockquote>
<h2>v2.16.0 (2026-09-16)</h2>
<ul>
<li>Update to Unicode 18.0</li>
<li>Update translations to CLDR release-49-alpha2</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/carpedm20/emoji/commit/d5d59d591f8590ada989f0bdf31603b3fe84e103"><code>d5d59d5</code></a>
Merge pull request <a
href="https://redirect.github.com/carpedm20/emoji/issues/335">#335</a>
from carpedm20/update-unicode-18.0</li>
<li><a
href="https://github.com/carpedm20/emoji/commit/7acc7f0740b0ded33c7450d788c6641b74e84e16"><code>7acc7f0</code></a>
update to Unicode 18.0.0</li>
<li>See full diff in <a
href="https://github.com/carpedm20/emoji/compare/v2.15.0...v2.16.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `seleniumbase` from 4.54.5 to 4.54.9
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.54.9 - MCP Server: Patch 17</h2>
<h2>MCP Server: Patch 17</h2>
<ul>
<li><a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4508/changes/e323a9d851b58a99b8b55b041218f415652a9fd6">Update
the MCP server</a></li>
<li><a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4508/commits/bc88accb2909629f7b0af68be73bebf014c17373">Refresh
Python dependencies</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>MCP Server: Patch 17 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4508">seleniumbase/SeleniumBase#4508</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.54.8...v4.54.9">https://github.com/seleniumbase/SeleniumBase/compare/v4.54.8...v4.54.9</a></p>
<h2>4.54.8 - Fix command injection and more</h2>
<h2>Fix command injection and more</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/7c8744798ed97616fe246effe982b93b0cc1d15e">Fix
command injection in console scripts</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/7c9871c812bf410164b3a6ed14aec9b9cfe2abd3">Fix
issue with clearing a field that has autocomplete</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/969a36c19fb3a05eec50821ad6be63805a68e21a">Fix
typos in behave_sb.py</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/47034f13729e9f24564c18ba1952b1307798f080">Refactor
console scripts</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/f7da99c23471a0f0a1101b0afd9a0a82d38a99ba">Update
the MCP server</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/4bc64fb41dcd6ea6030887456fe97de72a3443a7">Refresh
Python dependencies</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/c1b492a650c8077ae6ef43915b3a455fab8f4e8d">Update
examples</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>Fix command injection and more by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4507">seleniumbase/SeleniumBase#4507</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.54.7...v4.54.8">https://github.com/seleniumbase/SeleniumBase/compare/v4.54.7...v4.54.8</a></p>
<h2>4.54.7 - MCP Server: Patch 16</h2>
<h2>MCP Server: Patch 16</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/534c0c124002bfb48e10a7198194c91c61f4d4db">Update
the MCP server</a>
--&gt; annotations and docstrings</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/4a5453ee14dff60599b058669eaff025488b75d2">Refresh
Python dependencies</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>MCP Server: Patch 16 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4505">seleniumbase/SeleniumBase#4505</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.54.6...v4.54.7">https://github.com/seleniumbase/SeleniumBase/compare/v4.54.6...v4.54.7</a></p>
<h2>4.54.6 - MCP Server: Patch 15</h2>
<h2>MCP Server: Patch 15</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/fbc665f3cb19ea6e5eb4ed6ffcf4eec32e4d9580">Add
tool annotations to the MCP Server</a>
--&gt; This resolves <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4503">seleniumbase/SeleniumBase#4503</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/a406b7f277de3a56dc3d83d4b5466dbca34a35e4">Update
the Dockerfile</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/8c9805f870bd1a2a0bb9b8ef79e710848a9e81f2">Update
CDP Mode</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/7f488faed38e8afce01d18b30291906e75dfe72d">Update
uv</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/b4910211a8e801eebab9f0b1a6679bf1077ac53f">Update
CDP Mode examples</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>MCP Server: Patch 15 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4504">seleniumbase/SeleniumBase#4504</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/ddd0f047e522c584221176bccf078784cac03b00"><code>ddd0f04</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4508">#4508</a>
from seleniumbase/mcp-server-patch-17</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/918078082a49b2a57f3a33fc9a4149973f780e64"><code>9180780</code></a>
Version 4.54.9</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/bc88accb2909629f7b0af68be73bebf014c17373"><code>bc88acc</code></a>
Refresh Python dependencies</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/d10f01b50168d62aea34bdc756ae249f75fdf642"><code>d10f01b</code></a>
Update MCP versioning</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/e323a9d851b58a99b8b55b041218f415652a9fd6"><code>e323a9d</code></a>
Update the MCP server</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/52ceddcfbc466c937f88b322cfa69f11600b1e0a"><code>52ceddc</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4507">#4507</a>
from seleniumbase/fix-command-injection-and-more</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/1b18c10456cc064ce38e7024913b285b3053197a"><code>1b18c10</code></a>
Version 4.54.8</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/c1b492a650c8077ae6ef43915b3a455fab8f4e8d"><code>c1b492a</code></a>
Update examples</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/a64c668254f20c3384c9f0ec1635de7e9eba45ce"><code>a64c668</code></a>
Update MCP versioning</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/4bc64fb41dcd6ea6030887456fe97de72a3443a7"><code>4bc64fb</code></a>
Refresh Python dependencies</li>
<li>Additional commits viewable in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.54.5...v4.54.9">compare
view</a></li>
</ul>
</details>
<br />

Updates `ruff` from 0.16.7 to 0.16.8
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/releases">ruff's
releases</a>.</em></p>
<blockquote>
<h2>0.16.8</h2>
<h2>Release Notes</h2>
<p>Released on 2026-09-16.</p>
<h3>Bug fixes</h3>
<ul>
<li>Visit functional <code>TypedDict</code> keyword arguments correctly
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/28584">#28584</a>)</li>
<li>[<code>flake8-simplify</code>] Detect nested <code>async with</code>
under sync parent (<code>SIM117</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27821">#27821</a>)</li>
<li>[<code>flake8-simplify</code>] Preserve operand order in
<code>SIM109</code> fix (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27824">#27824</a>)</li>
<li>[<code>pyupgrade</code>] Preserve required parentheses in multiline
<code>UP040</code> fixes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28164">#28164</a>)</li>
<li>[<code>pyupgrade</code>] Skip <code>TypeVarTuple</code> and
<code>ParamSpec</code> conversions with bounds or constraints
(<code>UP040</code>, <code>UP046</code>, <code>UP047</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28505">#28505</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>Add support for <code>__lazy_modules__</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28459">#28459</a>)</li>
<li>Recognize PEP-728 <code>TypedDict</code> class keywords (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28533">#28533</a>)</li>
<li>Recognize quoted types in <code>typing.TypeForm</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28507">#28507</a>)</li>
<li>Support conditional assignment to <code>__lazy_modules__</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28491">#28491</a>)</li>
<li>[<code>flake8-type-checking</code>] Prefer lazy imports over
<code>TYPE_CHECKING</code> on Python 3.15 and later (<code>TC001</code>,
<code>TC002</code>, <code>TC003</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28541">#28541</a>)</li>
<li>[<code>pyupgrade</code>] Make the fix for <code>UP040</code> always
unsafe (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28526">#28526</a>)</li>
<li>[<code>pyupgrade</code>] Stop recommending deprecated
<code>ByteString</code> aliases (<code>UP035</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28498">#28498</a>)</li>
<li>[<code>ruff</code>, <code>flake8-use-pathlib</code>] Recognize the
<code>parent_mode</code> argument (<code>RUF064</code>,
<code>PTH103</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28528">#28528</a>)</li>
<li>[<code>ruff</code>] Detect <code>\Z</code> in
<code>pytest.raises()</code> match patterns (<code>RUF043</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28598">#28598</a>)</li>
</ul>
<h3>CLI</h3>
<ul>
<li>Use rule name and code in formatter incompatibility warnings (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28571">#28571</a>)</li>
</ul>
<h3>Configuration</h3>
<ul>
<li>[<code>flake8-tidy-imports</code>] Add
<code>extend-banned-api</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28644">#28644</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/VedantMadane"><code>@​VedantMadane</code></a></li>
<li><a href="https://github.com/alzeph"><code>@​alzeph</code></a></li>
<li><a
href="https://github.com/charliermarsh"><code>@​charliermarsh</code></a></li>
<li><a
href="https://github.com/fredrikblau"><code>@​fredrikblau</code></a></li>
<li><a href="https://github.com/ntBre"><code>@​ntBre</code></a></li>
<li><a href="https://github.com/zsol"><code>@​zsol</code></a></li>
<li><a
href="https://github.com/Aniket-a14"><code>@​Aniket-a14</code></a></li>
<li><a href="https://github.com/r-b-1"><code>@​r-b-1</code></a></li>
</ul>
<h2>Install ruff 0.16.8</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://releases.astral.sh/github/ruff/releases/download/0.16.8/ruff-installer.sh
| sh
&lt;/tr&gt;&lt;/table&gt; 
</code></pre>
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's
changelog</a>.</em></p>
<blockquote>
<h2>0.16.8</h2>
<p>Released on 2026-09-16.</p>
<h3>Bug fixes</h3>
<ul>
<li>Visit functional <code>TypedDict</code> keyword arguments correctly
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/28584">#28584</a>)</li>
<li>[<code>flake8-simplify</code>] Detect nested <code>async with</code>
under sync parent (<code>SIM117</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27821">#27821</a>)</li>
<li>[<code>flake8-simplify</code>] Preserve operand order in
<code>SIM109</code> fix (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27824">#27824</a>)</li>
<li>[<code>pyupgrade</code>] Preserve required parentheses in multiline
<code>UP040</code> fixes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28164">#28164</a>)</li>
<li>[<code>pyupgrade</code>] Skip <code>TypeVarTuple</code> and
<code>ParamSpec</code> conversions with bounds or constraints
(<code>UP040</code>, <code>UP046</code>, <code>UP047</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28505">#28505</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>Add support for <code>__lazy_modules__</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28459">#28459</a>)</li>
<li>Recognize PEP-728 <code>TypedDict</code> class keywords (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28533">#28533</a>)</li>
<li>Recognize quoted types in <code>typing.TypeForm</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28507">#28507</a>)</li>
<li>Support conditional assignment to <code>__lazy_modules__</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28491">#28491</a>)</li>
<li>[<code>flake8-type-checking</code>] Prefer lazy imports over
<code>TYPE_CHECKING</code> on Python 3.15 and later (<code>TC001</code>,
<code>TC002</code>, <code>TC003</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28541">#28541</a>)</li>
<li>[<code>pyupgrade</code>] Make the fix for <code>UP040</code> always
unsafe (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28526">#28526</a>)</li>
<li>[<code>pyupgrade</code>] Stop recommending deprecated
<code>ByteString</code> aliases (<code>UP035</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28498">#28498</a>)</li>
<li>[<code>ruff</code>, <code>flake8-use-pathlib</code>] Recognize the
<code>parent_mode</code> argument (<code>RUF064</code>,
<code>PTH103</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28528">#28528</a>)</li>
<li>[<code>ruff</code>] Detect <code>\Z</code> in
<code>pytest.raises()</code> match patterns (<code>RUF043</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28598">#28598</a>)</li>
</ul>
<h3>CLI</h3>
<ul>
<li>Use rule name and code in formatter incompatibility warnings (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28571">#28571</a>)</li>
</ul>
<h3>Configuration</h3>
<ul>
<li>[<code>flake8-tidy-imports</code>] Add
<code>extend-banned-api</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28644">#28644</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/VedantMadane"><code>@​VedantMadane</code></a></li>
<li><a href="https://github.com/alzeph"><code>@​alzeph</code></a></li>
<li><a
href="https://github.com/charliermarsh"><code>@​charliermarsh</code></a></li>
<li><a
href="https://github.com/fredrikblau"><code>@​fredrikblau</code></a></li>
<li><a href="https://github.com/ntBre"><code>@​ntBre</code></a></li>
<li><a href="https://github.com/zsol"><code>@​zsol</code></a></li>
<li><a
href="https://github.com/Aniket-a14"><code>@​Aniket-a14</code></a></li>
<li><a href="https://github.com/r-b-1"><code>@​r-b-1</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ruff/commit/62914c4b9b79a9e5004374a9c482ad2ed69290e1"><code>62914c4</code></a>
Bump version to 0.16.8 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28648">#28648</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/c47e0cdc665f56536ce7f7a8ac40fa0ff3f79482"><code>c47e0cd</code></a>
[ty] Bound aliased intersection expansion during inference (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28546">#28546</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/ff4747b509ab4fffbe5689fcae39caa7503d1dcf"><code>ff4747b</code></a>
renovate: update uv hashes correctly with setup-uv (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28621">#28621</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/94efeaa28630d80b2a74adf3c3963de99ed4ee29"><code>94efeaa</code></a>
[ty] Compact reachable binding and declaration histories (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28349">#28349</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/50020fb1e8aa83b0826fa6f5f33a1e93c10cc60e"><code>50020fb</code></a>
[ty] Avoid storing constraint nodes twice (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28375">#28375</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/446bb68da50014bb75f5ce1504a80c5883e3b0b2"><code>446bb68</code></a>
[ty] Compare bound-method receivers before signatures (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28384">#28384</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/304ab86be5de6507e276ab09f5b43f44aeb92469"><code>304ab86</code></a>
[<code>flake8-type-checking</code>] Prefer lazy imports over
<code>TYPE_CHECKING</code> on 3.15+ (`...</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/d940b244f7512427b0d87c7953e88c60e69f9bdf"><code>d940b24</code></a>
[ty] Watch script dependencies in CLI watch mode (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28125">#28125</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/fe9f065a504127b11da72c2ff6d7813ddf3ce8ac"><code>fe9f065</code></a>
[flake8-tidy-imports] Add <code>extend-banned-api</code> (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28644">#28644</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/31131db44f057cce68fa6b95552b7db54167b0b3"><code>31131db</code></a>
[ty] Support <code>type[A &amp; B]</code> (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27124">#27124</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ruff/compare/0.16.7...0.16.8">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-21 00:44:08 -04:00
CaliBrain e1c3f057ab fix: bypass recordings, welib wrong-md5 links, footer build sha (#1364) (#1373)
Debug screen recordings never started. Every bypass logged "Capturearea
1540x1050 at position 0.0 outside the screen size 1440x1880".We ask
ffmpeg for the fingerprint screen size plus margin, the size wealso pass
SeleniumBase as xvfb_metrics. SeleniumBase builds thatdisplay with
use_xauth=True, the image ships no xauth binary, so itfalls back to a
fixed 1440x1880 Xvfb and the requested size neverexists. Drop
-video_size so x11grab records the whole screen, whateversize it turned
out to be.

welib could hand back a link for a different book. Welib
answers/md5/<md5> with a search for that md5; when it does not have the
file,the resolver took the first "Download" on the results page
(md5a2c1dc0c... resolved to auto_download/9c8cf85d...). On an
/md5/<md5>page a GET/Download link is now only taken when its href names
thatmd5; otherwise the source is reported as not having the file.
Alsoremoves _get_download_urls_from_welib and _is_source_enabled:
themd5-template branch in _get_urls_for_source always handles welib
first,so that resolver could never run.

The footer showed the build date instead of the commit. CI
stampsBUILD_VERSION as <yyyy-mm-dd>-<sha> (pr-<sha> for PR images) and
thefooter kept its first seven characters, so dev images read
"Shelfmarkmain (2026-09)". Take the trailing commit sha instead: "main
(1a5b37d)".The full BUILD_VERSION stays in the hover title
2026-09-21 00:15:41 -04:00
CaliBrain d978896142 fix(auth): rename the API_KEY env var to SHELFMARK_API_KEY (#1374) 2026-09-21 00:10:10 -04:00
Gavin McFallandClaude Fable 5.1 3b280009ae feat(auth): static API_KEY (env) accepted as Bearer or X-Api-Key, cookie or key (#1366)
Supersedes #1353, per the discussion in #1352: one `API_KEY` environment
variable; when set, a request carrying it is authenticated as the first
admin, and cookie sessions keep working exactly as before (cookie **or**
key). Nothing else changes. No table, no UI, no settings-tab switch, no
per-user keys.

## What

- `API_KEY` (env). Unset → the feature is off and none of the new code
runs.
- `Authorization: Bearer <key>` or `X-Api-Key: <key>` on any existing
`/api/*` route authenticates that request as the first admin in
`users.db` (`ORDER BY id`), or as a bare admin identity
(`user_id="api"`, `is_admin=True`, no local user row) if the install has
no admin yet. Per request only; nothing is persisted; the admin's role
is read live, so deleting or demoting that user takes effect on the next
request.
- Both headers are checked and either may match. That is what makes the
key usable behind a reverse proxy that injects its own `Authorization`
header (oauth2-proxy, Authelia, forwardAuth): send the key in
`X-Api-Key`.
- A credential that is **not** the key is ignored and the request
continues on the normal session path, so proxy-forwarded tokens are
unaffected. Without a valid session such a request gets the usual `401
{"error": "Unauthorized"}`, identical to a request with no credential,
so there is nothing to probe.

## How

- `shelfmark/config/env.py`: `API_KEY = os.getenv("API_KEY",
"").strip()`.
- `shelfmark/core/api_key.py`: `extract_api_key_candidates()` (Bearer
token if the scheme is Bearer, then `X-Api-Key`) and `matches_api_key()`
using `hmac.compare_digest` on bytes.
- `shelfmark/core/user_db.py`: `UserDB.get_first_admin()`.
- `shelfmark/main.py`: `api_key_auth_middleware` (`before_request`,
registered before `proxy_auth_middleware`, which early-returns for keyed
requests). Only `/api/` paths; `/api/health` and `/api/auth/*` exempt;
no-op when `API_KEY` is unset or the auth mode is `none`. On a match it
mirrors the proxy-auth pattern: `session.clear()` then populate
`user_id` / `is_admin` / `db_user_id` for this request, `permanent =
False`, `modified = False`, `g.api_key_auth = True`. An `after_request`
hook guarantees no `Set-Cookie` is written for a keyed request even if a
handler dirties the session.
- `docs/api-access.md` (new), the `API_KEY` entry in
`docs/environment-variables.md`, and a README link.

## Security

- Constant-time compare; the key is never logged or echoed.
- Keyed requests never mint or refresh a session cookie and ignore any
cookie sent with them (a non-admin cookie plus the key yields admin for
that request; the browser's own session is left untouched and usable).
- The mismatch path touches neither the session nor `g`, so a stray
bearer on a browser request can neither log the user out nor change how
their cookie is refreshed.
- Store errors during the admin lookup fail closed (`500 {"error":
"Authentication error"}`), never to anonymous.
- Verified against Flask's `save_session` / `should_set_cookie`
ordering, and under auth modes `none`, `builtin`, `proxy`.

## Tests

`tests/core/test_api_key_env.py` (36): extraction and matching;
first-admin lookup; middleware behaviour on a guarded route and an admin
route, with and without a user_db, `X-Api-Key`, both-headers
combinations, no `Set-Cookie` when a handler dirties the session,
incoming non-admin cookie ignored, browser cookie still usable after a
keyed request, security headers, store error → 500, mismatch → guard's
401 / cookie path / permanent cookie untouched, unset → off, exempt
paths and path probes, `none` and `proxy` modes, deleted and demoted
first admin, a keyed write passing the guard. Existing auth suites
unchanged. All CI gates green on the fork:
https://github.com/gavinmcfall/shelfmark/pull/2 (CI-only draft).

Also exercised against a running instance: 47 scripted checks including
150 concurrent requests, proxy-mode switching through the key, an
unset-key restart, and a log scan for the key.

## Naming

`API_KEY` as discussed. If you'd rather namespace it
(`SHELFMARK_API_KEY`) to avoid clashing with other tools' env vars in
shared compose files, it is a one-line change; say the word.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 00:00:42 -04:00
splitsec2 1a5b37d9d3 fix(deluge): send seeding ratio limit under Deluge's own keys (#1367)
Deluge's per-torrent options are `stop_at_ratio` (bool) and `stop_ratio`
(float), and `torrentmanager` checks `options['stop_at_ratio'] and
get_ratio() >= options['stop_ratio']`. We were putting the indexer's
float into `stop_at_ratio`, which only switched stopping on and left the
daemon's global ratio (default 2.0) as the one actually enforced. A
`ratio_limit` of 0 turned stopping off entirely. `stop_at_ratio_enabled`
is not a Deluge option at all.

Deluge has no per-torrent seeding time limit, `seed_time_limit` is a
global core preference, so the value is logged as unapplied instead of
sent as a key the daemon drops.

qBittorrent and Transmission already honour both indexer limits, so this
removes a silent difference between clients.

## Verification

- `tests/prowlarr/test_deluge_client.py`: the ratio arrives as
`stop_ratio` with `stop_at_ratio` set, and no key Deluge does not define
is sent. Both fail on current main and pass here.
- Full suite (3165), ruff, ruff format, basedpyright, vulture green.
2026-09-20 23:06:14 -04:00
splitsec2 7c8e89c567 fix(googlebooks): page by the capped size, not the raw limit (#1370)
The Google Books search builds `maxResults` as `min(limit, 40)` because
the API caps a page at 40 volumes, but advances `startIndex` by the full
`limit`. The pages then stop tiling. With `limit=50`, page 1 covers
items 0 to 39 and page 2 starts at 50, so items 40 to 49 are never
returned and every later page drops another 10.

This computes the page size once and uses it for both `maxResults` and
`startIndex`. The shipped frontend asks for 40 and is unaffected.
`/api/metadata/search` clamps `limit` to 100, so an API caller passing
41 to 100 was hitting it.

One thing I left alone. The provider uses the base `search_paginated`
heuristic, `has_more = len(books) >= options.limit`, which still reports
`has_more: false` for a limit above 40 since Google can never return
that many. That was already the behaviour before this change, and fixing
it means either touching the shared heuristic or adding a provider
override, so I kept this patch to the stride. Happy to follow up if you
want it.

## Verification

- `tests/metadata/test_googlebooks_parse.py`: pages 1 and 2 at
`limit=50` must tile exactly, plus a guard that `limit=25` still strides
by 25. The first fails on current main and passes here.
- Full suite (3165), ruff, ruff format, basedpyright, vulture green.
2026-09-20 23:05:48 -04:00
splitsec2 bc03ad062e fix(http): keep the host of a protocol-relative download link (#1368)
`get_absolute_url()` replaced both `netloc` and `scheme` whenever either
one was missing. A protocol-relative href such as
`//cdn.example.org/f.epub`, scraped from a page on
`https://annas-archive.org/...`, parses with a netloc and an empty
scheme, so it came back pointing at the page's own host. The download
then 404s and the source is skipped.

Each field now falls back to the base URL only when the parsed URL does
not supply it. Plain relative paths resolve exactly as before, which the
control test covers.

This affects the Z-Library, welib and generic download link handling in
`release_sources/direct_download/annas_archive.py`.

## Verification

- New `tests/download/test_http_absolute_url.py`: a protocol-relative
link keeps its own host, and a plain `/path` still resolves against the
base. The first fails on current main and passes here.
- Full suite (3165), ruff, ruff format, basedpyright, vulture green.
2026-09-20 23:04:22 -04:00
splitsec2 ab3aa9a8b0 fix(requests): reject non-object items in the batch endpoint (#1369)
`POST /api/requests/batch` checks that `requests` is a non-empty list
and then hands each element to the shared preparation helper, which
calls `.get()` on it. A bare string, number or null in the list raises
`AttributeError` and the caller gets a 500, while `POST /api/requests`
answers 400 with a message for the same mistake.

This validates the element type beside the existing list check. One bad
item rejects the whole batch rather than being reported per item, which
matches the endpoint's current contract: every other failure path
already aborts the batch with a single error body, as
`test_batch_create_requests_is_atomic` asserts. Responses for valid
payloads are unchanged.

## Verification

- `tests/core/test_request_routes_api.py`: a case per bad shape (int,
str, null, list), plus a mixed valid and invalid batch that asserts
nothing was created. All fail on current main with a 500 and pass here.
- Full suite (3168), ruff, ruff format, basedpyright, vulture green.
2026-09-20 23:03:31 -04:00
Paul Rimmer d4619be69a Feature: Show the AA search result stats (#1362)
When doing a direct search for a book, show the stats of the AA results.
For example if we search for "The Great Gatsby", AA reports it has 240
hits and shows the first page of 50. Provide this stats info in the
shelfmark webUI via ResultsSection.tsx and ReleaseModal.tsx. This table
shows what should be displayed based on the total number of hits found:

|Total	|Display|
|-------|-------|
|1	|"Result 1 (1 Total)"|
|6	|"Results 1-6 (6 Total)"|
|144	|"Results 1-50 (144 Total)"|
|500+	|"Results 1-50 (500+ Total)"|

Currently, shelfmark also only shows us the first 50 search hits even if
there were more available from AA. This could be added later if
considered desirable.

As usual, a picture is worth a 1000 words:

<img width="1012" height="610" alt="direct-results-stat"
src="https://github.com/user-attachments/assets/8c0c688f-444e-482a-a9a3-2dee5a5563b8"
/>

<img width="1013" height="741" alt="universal-results-info"
src="https://github.com/user-attachments/assets/399bbb79-87f2-4432-a3d0-64937795f5f1"
/>

Coded with llama.cpp, opencode and 🤖
2026-09-20 13:03:32 -04:00
splitsec2 c1315a2b23 fix(download): check task ownership before serving queued files (#1357)
`/api/localdownload` resolves the file through the live queue and
returns it before checking who owns the task; the owner check only runs
on the download-history fallback, once the task has aged out of the
queue. Task ids are source ids, so two users who searched the same book
can end up with the same id.

This applies the same rule on the queue path, reusing the
`_task_owned_by_actor` helper the cancel/retry/priority routes already
use, so both paths answer a non-owner with the same 404. Admin behaviour
is unchanged.

The 404 matches what this endpoint's history path already returns for a
non-owner rather than the 403 `download_not_owned` the
cancel/retry/priority routes use, happy to switch it if you prefer
consistency with the siblings instead.

## Verification

- `tests/core/test_activity_routes_api.py`: the owner still receives
their queued file; a different user receives 404. The new case fails on
current main and passes here; the existing history-fallback test is
unchanged.
- Full suite (3148), ruff, ruff format, basedpyright, vulture green.
2026-09-20 12:56:23 -04:00
splitsec2 7934924678 fix(queue): don't stamp CANCELLED over a finished download (#1361)
`cancel_download` reads the task status under the queue lock, releases
it, and only then writes CANCELLED through `update_status`. A download
that finishes in that window has its COMPLETE overwritten. The queue and
the UI show the task as cancelled while the file is already on disk, and
the terminal hook fires for both statuses.

The check and the write now happen in a single lock hold. Because the
lock is non-reentrant and the terminal hook has to run after it is
released (the stall canceller depends on that), the lock-held part of
`update_status` moved into a small private helper that both paths share;
`update_status` is a thin wrapper over it. A cancel arriving once the
task is already terminal still returns `False`.

## Verification

-
`tests/core/test_queue.py::test_cancel_does_not_overwrite_a_download_that_finished_first`:
a worker thread completes the download while the cancel is in flight,
with the handover driven by events rather than sleeps. The task stays
complete. Fails on main, passes here.
- Full suite (3147), plus `tests/download/` and
`tests/core/test_download_api_guardrails.py`, ruff, ruff format,
basedpyright, vulture green.
2026-09-20 12:54:05 -04:00
splitsec2 a6204a318e fix(oidc): reject backslash paths in the return_to sanitizer (#1359)
The OIDC `return_to` sanitizer rejects values starting with `//` and
then relies on `urlsplit` to catch anything carrying a netloc. A value
such as `/\host` has no netloc, so it is stored in the session and used
as the post-login redirect target and browsers resolve the backslash as
a path separator, which lands the user outside the app after a
successful login.

`_normalize_return_to` now also rejects values whose path contains a
backslash. That matches the frontend sanitizer in `authRedirect.ts`,
which parses with `URL` and already discards those forms, so the two
ends agree again. The check covers the path only, so query and fragment
backslashes still round-trip, and it also catches the script-root case
where `/app/\host` strips to `/\host`.

## Verification

- New cases in `tests/core/test_oidc_routes.py` cover the rejected
forms, including under a script root, and confirm `/`, `/settings` and
`/search?q=x#frag` are unaffected. They fail on current main and pass
here.
- Full suite (3155), ruff, ruff format, basedpyright, vulture green.
2026-09-20 12:51:21 -04:00
splitsec2 545480c557 fix(download): default is_admin to False in the request policy guard (#1358)
`_resolve_policy_mode_for_current_user` reads `session.get("is_admin",
True)`, so a session carrying `user_id` but no `is_admin` key skips the
request policy entirely, while every other admin check in the codebase
defaults the key to `False`.

This uses the same default here. Every authenticated login path
(builtin, CWA, proxy, OIDC) writes `is_admin` into the session, and
`AUTH_METHOD=none` is already short-circuited one line earlier, so
sessions from those flows behave exactly as before.

## Verification

- `tests/core/test_request_routes_api.py::TestDownloadPolicyGuards`: a
session without `is_admin` now gets `policy_requires_request` and
nothing is queued. Fails on current main, passes here.
- Full suite (3147), ruff, ruff format, basedpyright, vulture green.
2026-09-20 12:50:53 -04:00
splitsec2 127dd82615 fix(users): apply user updates only after the payload validates (#1360)
`PUT /api/users/me` and `PUT /api/admin/users/<id>` write the new
password hash, and then the profile fields, before the rest of the
payload is checked. When the request is rejected further down as an
invalid role, an admin-only setting, an invalid settings value, the
route answers 400 with those writes already committed, so the caller
sees an error while the password has in fact changed.

Both routes now validate the whole payload before touching the database,
and the password hash is folded into the same `update_user` call as the
other fields so the field write is a single transaction. Error messages,
status codes and the order they are reported in are unchanged.

## Verification

- New tests in `tests/core/test_self_user_routes.py` and
`tests/core/test_admin_users_api.py` assert that a rejected update
leaves the password, profile fields and role as they were, plus a
positive case that a valid payload still applies all three. They fail on
current main and pass here.
- Full suite (3150), ruff, ruff format, basedpyright, vulture green.
2026-09-20 12:50:14 -04:00
splitsec2 acd59f7cbb feat(auth): provision proxy users as non-admin once an admin exists (#1356)
With `AUTH_METHOD=proxy` and no admin group configured, every user the
proxy authenticates for the first time is provisioned as an admin
(`is_admin = True` unless the user already exists in `users.db`). The
intent to never lock an instance out makes sense, but the effect is that
anyone the SSO gate lets through becomes an administrator. On an
instance shared with family or a small community that is a footgun; I
hit it when the first invited reader landed as an admin.

This keeps the guarantee and removes the footgun: the first account is
still provisioned as an admin while the instance has no admin at all,
and later first-time users follow a new `PROXY_AUTH_DEFAULT_ROLE`
setting (Security tab / env), default `user`. Known users keep their
stored role; the `PROXY_AUTH_ADMIN_GROUP_NAME` path is unchanged and
still takes precedence. I couldn't find a way with Cloudflare access to
pass this along.

Changes: `UserDB.has_admin()`, `_proxy_default_is_admin()` in the proxy
middleware, the new `SelectField` beside the other proxy settings, the
regenerated `docs/environment-variables.md` entry and a row in
`docs/reverse-proxy.md`.

Compatibility: the default moves from "everyone admin" to "first admin,
then users". Accounts already in `users.db` are unaffected; new SSO
users on an existing instance become regular users unless
`PROXY_AUTH_DEFAULT_ROLE=admin` is set. If you would rather ship this
purely opt-in I can flip the default to `admin`.

## Verification

- `tests/core/test_auth_api.py::TestProxyProvisioningRole`: first user
admin / second user not; `PROXY_AUTH_DEFAULT_ROLE=admin` restores the
old behaviour; an admin from another auth source counts as "an admin
exists"; a known user keeps their role whatever the default.
- Full suite (3094), ruff, ruff format, basedpyright, vulture green.
- Running on my own instance since 2026-09-19.
2026-09-19 23:27:13 -04:00
dependabot[bot] c42edac363 build(deps): bump the gh-actions group with 6 updates (#1350)
Bumps the gh-actions group with 6 updates:

| Package | From | To |
| --- | --- | --- |
|
[docker/setup-buildx-action](https://github.com/docker/setup-buildx-action)
| `4.3.0` | `4.4.0` |
|
[docker/build-push-action](https://github.com/docker/build-push-action)
| `7.3.0` | `7.4.0` |
| [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) | `10.0.1`
| `10.1.0` |
| [github/codeql-action/init](https://github.com/github/codeql-action) |
`4.37.9` | `4.38.0` |
|
[github/codeql-action/autobuild](https://github.com/github/codeql-action)
| `4.37.9` | `4.38.0` |
|
[github/codeql-action/analyze](https://github.com/github/codeql-action)
| `4.37.9` | `4.38.0` |

Updates `docker/setup-buildx-action` from 4.3.0 to 4.4.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/docker/setup-buildx-action/releases">docker/setup-buildx-action's
releases</a>.</em></p>
<blockquote>
<h2>v4.4.0</h2>
<ul>
<li>Use official Buildx releases for cloud driver by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/606">docker/setup-buildx-action#606</a></li>
<li>Pull BuildKit image before builder creation by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/609">docker/setup-buildx-action#609</a></li>
<li>Use shared error helpers for Buildx and Docker commands by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/620">docker/setup-buildx-action#620</a></li>
<li>Bump <code>@​docker/actions-toolkit</code> from 0.95.0 to 0.100.0 in
<a
href="https://redirect.github.com/docker/setup-buildx-action/pull/610">docker/setup-buildx-action#610</a>
<a
href="https://redirect.github.com/docker/setup-buildx-action/pull/618">docker/setup-buildx-action#618</a>
<a
href="https://redirect.github.com/docker/setup-buildx-action/pull/619">docker/setup-buildx-action#619</a></li>
<li>Bump <code>@​humanfs/node</code> from 0.16.7 to 0.16.8 in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/614">docker/setup-buildx-action#614</a></li>
<li>Bump js-yaml from 5.3.0 to 5.4.2 in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/608">docker/setup-buildx-action#608</a>
<a
href="https://redirect.github.com/docker/setup-buildx-action/pull/617">docker/setup-buildx-action#617</a></li>
<li>Bump postcss-selector-parser from 7.1.1 to 7.1.5 in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/611">docker/setup-buildx-action#611</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/setup-buildx-action/compare/v4.3.0...v4.4.0">https://github.com/docker/setup-buildx-action/compare/v4.3.0...v4.4.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/594f3bf4285d9ea8dc53c9a0c9c4092420091003"><code>594f3bf</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/setup-buildx-action/issues/609">#609</a>
from crazy-max/pull-buildkit-image-before-create</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/bd6e702fc33b636671900d5b5edfab64698c9c25"><code>bd6e702</code></a>
chore: update generated content</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/6268c9da9abbd1309c8a16a75f92a878715c3032"><code>6268c9d</code></a>
pull BuildKit image before builder creation</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/e8235251b82e23c90e6fad50016f0a78b7f28f11"><code>e823525</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/setup-buildx-action/issues/621">#621</a>
from docker/dependabot/github_actions/codeql-actions-...</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/533ed8ed095b0b133ef16fb495aad119524e220d"><code>533ed8e</code></a>
build(deps): bump the codeql-actions group with 2 updates</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/bedaf135699075c88620cd30772b9b6eadc9ba99"><code>bedaf13</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/setup-buildx-action/issues/620">#620</a>
from crazy-max/shared-error-helpers</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/d5079fba84d5edd23d25ba7f3045122175ca6ee2"><code>d5079fb</code></a>
chore: update generated content</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/226a61612ab71c26bbd805f955834049a4a9772f"><code>226a616</code></a>
use shared error helpers for Buildx and Docker commands</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/77ce7f4bab9bc462d825001cde7f82d409ee474c"><code>77ce7f4</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/setup-buildx-action/issues/619">#619</a>
from docker/dependabot/npm_and_yarn/docker/actions-to...</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/0dc1dc94f0f7c064e93fda72884ca71e054c0354"><code>0dc1dc9</code></a>
[dependabot skip] chore: update generated content</li>
<li>Additional commits viewable in <a
href="https://github.com/docker/setup-buildx-action/compare/37fe631027851001ddb9b187196cc803df7f5f0e...594f3bf4285d9ea8dc53c9a0c9c4092420091003">compare
view</a></li>
</ul>
</details>
<br />

Updates `docker/build-push-action` from 7.3.0 to 7.4.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/docker/build-push-action/releases">docker/build-push-action's
releases</a>.</em></p>
<blockquote>
<h2>v7.4.0</h2>
<ul>
<li>Use the shared error helper for Buildx commands by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/build-push-action/pull/1620">docker/build-push-action#1620</a></li>
<li>Prevent workflow command injection in metadata logs by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/build-push-action/pull/1617">docker/build-push-action#1617</a></li>
<li>Bump <code>@​docker/actions-toolkit</code> from 0.92.0 to 0.100.0 in
<a
href="https://redirect.github.com/docker/build-push-action/pull/1614">docker/build-push-action#1614</a>
<a
href="https://redirect.github.com/docker/build-push-action/pull/1618">docker/build-push-action#1618</a>
<a
href="https://redirect.github.com/docker/build-push-action/pull/1621">docker/build-push-action#1621</a></li>
<li>Bump <code>@​humanfs/node</code> from 0.16.7 to 0.16.8 in <a
href="https://redirect.github.com/docker/build-push-action/pull/1609">docker/build-push-action#1609</a></li>
<li>Bump brace-expansion from 1.1.13 to 1.1.18 in <a
href="https://redirect.github.com/docker/build-push-action/pull/1592">docker/build-push-action#1592</a></li>
<li>Bump csv-parse from 7.0.0 to 7.0.2 in <a
href="https://redirect.github.com/docker/build-push-action/pull/1613">docker/build-push-action#1613</a></li>
<li>Bump js-yaml from 4.3.0 to 4.3.2 in <a
href="https://redirect.github.com/docker/build-push-action/pull/1605">docker/build-push-action#1605</a>
<a
href="https://redirect.github.com/docker/build-push-action/pull/1615">docker/build-push-action#1615</a></li>
<li>Bump nanoid from 3.3.16 to 3.3.18 in <a
href="https://redirect.github.com/docker/build-push-action/pull/1611">docker/build-push-action#1611</a></li>
<li>Bump postcss from 8.5.10 to 8.5.25 in <a
href="https://redirect.github.com/docker/build-push-action/pull/1590">docker/build-push-action#1590</a></li>
<li>Bump postcss-selector-parser from 7.1.1 to 7.1.5 in <a
href="https://redirect.github.com/docker/build-push-action/pull/1606">docker/build-push-action#1606</a></li>
<li>Bump sigstore from 4.1.0 to 4.1.1 in <a
href="https://redirect.github.com/docker/build-push-action/pull/1577">docker/build-push-action#1577</a></li>
<li>Bump undici from 6.27.0 to 6.28.0 in <a
href="https://redirect.github.com/docker/build-push-action/pull/1594">docker/build-push-action#1594</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/build-push-action/compare/v7.3.0...v7.4.0">https://github.com/docker/build-push-action/compare/v7.3.0...v7.4.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/docker/build-push-action/commit/c3c9e263c25d99ce0380d002d59b67737d91b0dc"><code>c3c9e26</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/build-push-action/issues/1621">#1621</a>
from docker/dependabot/npm_and_yarn/docker/actions-t...</li>
<li><a
href="https://github.com/docker/build-push-action/commit/459b6741834dcd35f946352017e7675bd2089d42"><code>459b674</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/build-push-action/commit/4dedcb23c91d79c1629bf53ec2c3bcfffef5b34e"><code>4dedcb2</code></a>
chore(deps): Bump <code>@​docker/actions-toolkit</code> from 0.99.0 to
0.100.0</li>
<li><a
href="https://github.com/docker/build-push-action/commit/379bf63a979bd70751945601fa04c50674509952"><code>379bf63</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/build-push-action/issues/1620">#1620</a>
from crazy-max/buildx-error-message</li>
<li><a
href="https://github.com/docker/build-push-action/commit/9877975c9e0b0b661592ff61049069507f9bc2f6"><code>9877975</code></a>
chore: update generated content</li>
<li><a
href="https://github.com/docker/build-push-action/commit/7ed0556ffafb8eb312463411ef0a84a1dfe24d94"><code>7ed0556</code></a>
use the shared Buildx error summary helper</li>
<li><a
href="https://github.com/docker/build-push-action/commit/91670ba5a4df99a24efff8637a78c83fd1b0f6b1"><code>91670ba</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/build-push-action/issues/1618">#1618</a>
from docker/dependabot/npm_and_yarn/docker/actions-t...</li>
<li><a
href="https://github.com/docker/build-push-action/commit/80dbc8614a5c0ce4356740f69179cf829ecdc79a"><code>80dbc86</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/build-push-action/commit/50cac3a3b6f55e6015d6483d1dd72a3ecb90d20d"><code>50cac3a</code></a>
chore(deps): Bump <code>@​docker/actions-toolkit</code> from 0.98.0 to
0.99.0</li>
<li><a
href="https://github.com/docker/build-push-action/commit/03b4d6cac0163b44733e1fa60adfd6da560ee4d1"><code>03b4d6c</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/build-push-action/issues/1617">#1617</a>
from crazy-max/fix-metadata-workflow-commands</li>
<li>Additional commits viewable in <a
href="https://github.com/docker/build-push-action/compare/53b7df96c91f9c12dcc8a07bcb9ccacbed38856a...c3c9e263c25d99ce0380d002d59b67737d91b0dc">compare
view</a></li>
</ul>
</details>
<br />

Updates `astral-sh/setup-uv` from 10.0.1 to 10.1.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/setup-uv/releases">astral-sh/setup-uv's
releases</a>.</em></p>
<blockquote>
<h2>v10.1.0 🌈 New output <code>python-runtime-id</code>and respect
NO_PROXY</h2>
<h2>Changes</h2>
<p>This release adds more bheind the scene security improvements and
also 2 small improvements.</p>
<h3>NO_PROXY</h3>
<p>This action now respects <code>no_proxy/NO_PROXY</code> environment
variables which were previously ignored.</p>
<h3>New output <code>python-runtime-id</code></h3>
<p>The new output <code>python-runtime-id</code> can be used to know
which python version exactly was installed if you use
<code>activate-environment</code>. See <a
href="https://redirect.github.com/pyca/cryptography/pull/15572#discussion_r3913508686">pyca/cryptography#15572</a>
for details on why this can be useful.</p>
<h2>🐛 Bug fixes</h2>
<ul>
<li>fix: respect no proxy directive <a
href="https://github.com/mj0nez"><code>@​mj0nez</code></a> (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1037">#1037</a>)</li>
<li>Use JSON + a typed wrapper instead of TS codegen <a
href="https://github.com/woodruffw"><code>@​woodruffw</code></a> (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1025">#1025</a>)</li>
</ul>
<h2>🚀 Enhancements</h2>
<ul>
<li>Expose a Python &quot;identity&quot; output <a
href="https://github.com/woodruffw"><code>@​woodruffw</code></a> (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1036">#1036</a>)</li>
<li>Verify downloads with astral-sh/versions checksums <a
href="https://github.com/zaniebot"><code>@​zaniebot</code></a> (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1033">#1033</a>)</li>
</ul>
<h2>🧰 Maintenance</h2>
<ul>
<li>chore: update known checksums for 0.12.12 @<a
href="https://github.com/apps/github-actions">github-actions[bot]</a>
(<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1041">#1041</a>)</li>
<li>chore: update known checksums for 0.12.10/0.12.11 @<a
href="https://github.com/apps/github-actions">github-actions[bot]</a>
(<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1038">#1038</a>)</li>
<li>chore: update known checksums for 0.12.9 @<a
href="https://github.com/apps/github-actions">github-actions[bot]</a>
(<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1035">#1035</a>)</li>
<li>chore: update known checksums for 0.12.7/0.12.8 @<a
href="https://github.com/apps/github-actions">github-actions[bot]</a>
(<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1031">#1031</a>)</li>
<li>chore: update known checksums for 0.12.6 @<a
href="https://github.com/apps/github-actions">github-actions[bot]</a>
(<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1030">#1030</a>)</li>
<li>chore: update known checksums for 0.12.5 @<a
href="https://github.com/apps/github-actions">github-actions[bot]</a>
(<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1020">#1020</a>)</li>
<li>Use self-repo syntax for all in-repo actions/reusable workflows <a
href="https://github.com/woodruffw"><code>@​woodruffw</code></a> (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1024">#1024</a>)</li>
<li>Pin one-shot tools <a
href="https://github.com/woodruffw"><code>@​woodruffw</code></a> (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1022">#1022</a>)</li>
<li>ci: remove obsolete direct push attempts <a
href="https://github.com/eifinger"><code>@​eifinger</code></a> (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1019">#1019</a>)</li>
</ul>
<h2>📚 Documentation</h2>
<ul>
<li>docs: update version references to v10.0.1 @<a
href="https://github.com/apps/github-actions">github-actions[bot]</a>
(<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1018">#1018</a>)</li>
</ul>
<h2>⬆️ Dependency updates</h2>
<ul>
<li>chore(deps-dev): roll up Dependabot updates <a
href="https://github.com/eifinger"><code>@​eifinger</code></a> (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1043">#1043</a>)</li>
<li>Harden npm install defaults <a
href="https://github.com/zaniebot"><code>@​zaniebot</code></a> (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1026">#1026</a>)</li>
<li>Add dependency cooldowns <a
href="https://github.com/woodruffw"><code>@​woodruffw</code></a> (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1021">#1021</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/bec219d24cd3e171d82865faccec33120bb574f4"><code>bec219d</code></a>
chore(deps-dev): roll up Dependabot updates (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1043">#1043</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/b90ec40d15bfa44c33c6700196eb6efcdddb4373"><code>b90ec40</code></a>
fix: respect no proxy directive (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1037">#1037</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/421feb646df5262e7dd93bc54161edfa30372417"><code>421feb6</code></a>
chore: update known checksums for 0.12.12 (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1041">#1041</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/f634bf473ad85bf3e23a613f52c5fa9f363874fc"><code>f634bf4</code></a>
Expose a Python &quot;identity&quot; output (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1036">#1036</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/a6772c8f0a09dc9e3582c70a994b0c55af921803"><code>a6772c8</code></a>
chore: update known checksums for 0.12.10/0.12.11 (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1038">#1038</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/e105c8fb1d7b13074b851babdaef4185243c6a07"><code>e105c8f</code></a>
chore: update known checksums for 0.12.9 (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1035">#1035</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/cd13f9217092d43a771cf9ba7b09bdd3da8d7c4d"><code>cd13f92</code></a>
Verify downloads with astral-sh/versions checksums (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1033">#1033</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/3aef7b92c52cec135792ea1e95f4c77683d39e61"><code>3aef7b9</code></a>
chore: update known checksums for 0.12.7/0.12.8 (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1031">#1031</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/d08d816a1ea176d61a318eff45abd3dffef415b1"><code>d08d816</code></a>
chore: update known checksums for 0.12.6 (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1030">#1030</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/19b4d1e990bec64818914c40230bde93a0de300b"><code>19b4d1e</code></a>
Harden npm install defaults (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1026">#1026</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/setup-uv/compare/20cfd1bf945f4377ade1205e4dbc17946fc9a30d...bec219d24cd3e171d82865faccec33120bb574f4">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/init` from 4.37.9 to 4.38.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/init's
releases</a>.</em></p>
<blockquote>
<h2>v4.38.0</h2>
<ul>
<li>On GitHub-hosted runners, the CodeQL Action now deletes unused
CodeQL bundles from the toolcache before downloading a different bundle,
which frees up disk space for the analysis. We expect to roll this
change out to everyone in September. <a
href="https://redirect.github.com/github/codeql-action/pull/4124">#4124</a></li>
<li>The CodeQL Action now supports CodeQL releases that are compatible
with Linux Arm64 and downloads the native <code>linux-arm64</code>
CodeQL bundle when available. <a
href="https://redirect.github.com/github/codeql-action/pull/4072">#4072</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.27.0">2.27.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4129">#4129</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/init's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.38.0 - 09 Sept 2026</h2>
<ul>
<li>On GitHub-hosted runners, the CodeQL Action now deletes unused
CodeQL bundles from the toolcache before downloading a different bundle,
which frees up disk space for the analysis. We expect to roll this
change out to everyone in September. <a
href="https://redirect.github.com/github/codeql-action/pull/4124">#4124</a></li>
<li>The CodeQL Action now supports CodeQL releases that are compatible
with Linux Arm64 and downloads the native <code>linux-arm64</code>
CodeQL bundle when available. <a
href="https://redirect.github.com/github/codeql-action/pull/4072">#4072</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.27.0">2.27.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4129">#4129</a></li>
</ul>
<h2>4.37.9 - 26 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.4">2.26.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4106">#4106</a></li>
</ul>
<h2>4.37.8 - 21 Aug 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.7 - 13 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/b96794f015dfd88f77b49b1c93e0fa7110f94c63"><code>b96794f</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4131">#4131</a>
from github/update-v4.38.0-7e08580a9</li>
<li><a
href="https://github.com/github/codeql-action/commit/02d5093871674ea20274117103ce3038c73c77ef"><code>02d5093</code></a>
Update changelog for v4.38.0</li>
<li><a
href="https://github.com/github/codeql-action/commit/7e08580a93dc4e4b9dda167e364577035cf504c6"><code>7e08580</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4130">#4130</a>
from github/henrymercer/workflow-runner-sizing</li>
<li><a
href="https://github.com/github/codeql-action/commit/bfcc52b4f5d98468a5993daa0bf0e4fb3f3ed698"><code>bfcc52b</code></a>
Run slow macOS checks on larger runners</li>
<li><a
href="https://github.com/github/codeql-action/commit/8c251e757c0260283fc50214a06ac768b61d3af4"><code>8c251e7</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4129">#4129</a>
from github/update-bundle/codeql-bundle-v2.27.0</li>
<li><a
href="https://github.com/github/codeql-action/commit/0b7ca400df35985869d4b9146a067865d4115da1"><code>0b7ca40</code></a>
Add changelog note</li>
<li><a
href="https://github.com/github/codeql-action/commit/40484b339517c6bcf00f81eebc95ca041ddca505"><code>40484b3</code></a>
Update default bundle to codeql-bundle-v2.27.0</li>
<li><a
href="https://github.com/github/codeql-action/commit/977e6ceaea7361825998245d787fa3b4d6b9e5df"><code>977e6ce</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4124">#4124</a>
from github/henrymercer/toolcache-bundle-cleanup</li>
<li><a
href="https://github.com/github/codeql-action/commit/40a6b3824794ae1156e1a5320d32e364bf1dcebc"><code>40a6b38</code></a>
Address toolcache cleanup review feedback</li>
<li><a
href="https://github.com/github/codeql-action/commit/deece8f852f048bc3f52fd42c9cc7a99b1ebb252"><code>deece8f</code></a>
Apply suggestion from <a
href="https://github.com/henrymercer"><code>@​henrymercer</code></a></li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/cdf488f595d80d6e07e03d4674febd5ab45fa938...b96794f015dfd88f77b49b1c93e0fa7110f94c63">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/autobuild` from 4.37.9 to 4.38.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/autobuild's
releases</a>.</em></p>
<blockquote>
<h2>v4.38.0</h2>
<ul>
<li>On GitHub-hosted runners, the CodeQL Action now deletes unused
CodeQL bundles from the toolcache before downloading a different bundle,
which frees up disk space for the analysis. We expect to roll this
change out to everyone in September. <a
href="https://redirect.github.com/github/codeql-action/pull/4124">#4124</a></li>
<li>The CodeQL Action now supports CodeQL releases that are compatible
with Linux Arm64 and downloads the native <code>linux-arm64</code>
CodeQL bundle when available. <a
href="https://redirect.github.com/github/codeql-action/pull/4072">#4072</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.27.0">2.27.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4129">#4129</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/autobuild's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.38.0 - 09 Sept 2026</h2>
<ul>
<li>On GitHub-hosted runners, the CodeQL Action now deletes unused
CodeQL bundles from the toolcache before downloading a different bundle,
which frees up disk space for the analysis. We expect to roll this
change out to everyone in September. <a
href="https://redirect.github.com/github/codeql-action/pull/4124">#4124</a></li>
<li>The CodeQL Action now supports CodeQL releases that are compatible
with Linux Arm64 and downloads the native <code>linux-arm64</code>
CodeQL bundle when available. <a
href="https://redirect.github.com/github/codeql-action/pull/4072">#4072</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.27.0">2.27.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4129">#4129</a></li>
</ul>
<h2>4.37.9 - 26 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.4">2.26.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4106">#4106</a></li>
</ul>
<h2>4.37.8 - 21 Aug 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.7 - 13 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/b96794f015dfd88f77b49b1c93e0fa7110f94c63"><code>b96794f</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4131">#4131</a>
from github/update-v4.38.0-7e08580a9</li>
<li><a
href="https://github.com/github/codeql-action/commit/02d5093871674ea20274117103ce3038c73c77ef"><code>02d5093</code></a>
Update changelog for v4.38.0</li>
<li><a
href="https://github.com/github/codeql-action/commit/7e08580a93dc4e4b9dda167e364577035cf504c6"><code>7e08580</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4130">#4130</a>
from github/henrymercer/workflow-runner-sizing</li>
<li><a
href="https://github.com/github/codeql-action/commit/bfcc52b4f5d98468a5993daa0bf0e4fb3f3ed698"><code>bfcc52b</code></a>
Run slow macOS checks on larger runners</li>
<li><a
href="https://github.com/github/codeql-action/commit/8c251e757c0260283fc50214a06ac768b61d3af4"><code>8c251e7</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4129">#4129</a>
from github/update-bundle/codeql-bundle-v2.27.0</li>
<li><a
href="https://github.com/github/codeql-action/commit/0b7ca400df35985869d4b9146a067865d4115da1"><code>0b7ca40</code></a>
Add changelog note</li>
<li><a
href="https://github.com/github/codeql-action/commit/40484b339517c6bcf00f81eebc95ca041ddca505"><code>40484b3</code></a>
Update default bundle to codeql-bundle-v2.27.0</li>
<li><a
href="https://github.com/github/codeql-action/commit/977e6ceaea7361825998245d787fa3b4d6b9e5df"><code>977e6ce</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4124">#4124</a>
from github/henrymercer/toolcache-bundle-cleanup</li>
<li><a
href="https://github.com/github/codeql-action/commit/40a6b3824794ae1156e1a5320d32e364bf1dcebc"><code>40a6b38</code></a>
Address toolcache cleanup review feedback</li>
<li><a
href="https://github.com/github/codeql-action/commit/deece8f852f048bc3f52fd42c9cc7a99b1ebb252"><code>deece8f</code></a>
Apply suggestion from <a
href="https://github.com/henrymercer"><code>@​henrymercer</code></a></li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/cdf488f595d80d6e07e03d4674febd5ab45fa938...b96794f015dfd88f77b49b1c93e0fa7110f94c63">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/analyze` from 4.37.9 to 4.38.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/analyze's
releases</a>.</em></p>
<blockquote>
<h2>v4.38.0</h2>
<ul>
<li>On GitHub-hosted runners, the CodeQL Action now deletes unused
CodeQL bundles from the toolcache before downloading a different bundle,
which frees up disk space for the analysis. We expect to roll this
change out to everyone in September. <a
href="https://redirect.github.com/github/codeql-action/pull/4124">#4124</a></li>
<li>The CodeQL Action now supports CodeQL releases that are compatible
with Linux Arm64 and downloads the native <code>linux-arm64</code>
CodeQL bundle when available. <a
href="https://redirect.github.com/github/codeql-action/pull/4072">#4072</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.27.0">2.27.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4129">#4129</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/analyze's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.38.0 - 09 Sept 2026</h2>
<ul>
<li>On GitHub-hosted runners, the CodeQL Action now deletes unused
CodeQL bundles from the toolcache before downloading a different bundle,
which frees up disk space for the analysis. We expect to roll this
change out to everyone in September. <a
href="https://redirect.github.com/github/codeql-action/pull/4124">#4124</a></li>
<li>The CodeQL Action now supports CodeQL releases that are compatible
with Linux Arm64 and downloads the native <code>linux-arm64</code>
CodeQL bundle when available. <a
href="https://redirect.github.com/github/codeql-action/pull/4072">#4072</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.27.0">2.27.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4129">#4129</a></li>
</ul>
<h2>4.37.9 - 26 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.4">2.26.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4106">#4106</a></li>
</ul>
<h2>4.37.8 - 21 Aug 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.7 - 13 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/b96794f015dfd88f77b49b1c93e0fa7110f94c63"><code>b96794f</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4131">#4131</a>
from github/update-v4.38.0-7e08580a9</li>
<li><a
href="https://github.com/github/codeql-action/commit/02d5093871674ea20274117103ce3038c73c77ef"><code>02d5093</code></a>
Update changelog for v4.38.0</li>
<li><a
href="https://github.com/github/codeql-action/commit/7e08580a93dc4e4b9dda167e364577035cf504c6"><code>7e08580</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4130">#4130</a>
from github/henrymercer/workflow-runner-sizing</li>
<li><a
href="https://github.com/github/codeql-action/commit/bfcc52b4f5d98468a5993daa0bf0e4fb3f3ed698"><code>bfcc52b</code></a>
Run slow macOS checks on larger runners</li>
<li><a
href="https://github.com/github/codeql-action/commit/8c251e757c0260283fc50214a06ac768b61d3af4"><code>8c251e7</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4129">#4129</a>
from github/update-bundle/codeql-bundle-v2.27.0</li>
<li><a
href="https://github.com/github/codeql-action/commit/0b7ca400df35985869d4b9146a067865d4115da1"><code>0b7ca40</code></a>
Add changelog note</li>
<li><a
href="https://github.com/github/codeql-action/commit/40484b339517c6bcf00f81eebc95ca041ddca505"><code>40484b3</code></a>
Update default bundle to codeql-bundle-v2.27.0</li>
<li><a
href="https://github.com/github/codeql-action/commit/977e6ceaea7361825998245d787fa3b4d6b9e5df"><code>977e6ce</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4124">#4124</a>
from github/henrymercer/toolcache-bundle-cleanup</li>
<li><a
href="https://github.com/github/codeql-action/commit/40a6b3824794ae1156e1a5320d32e364bf1dcebc"><code>40a6b38</code></a>
Address toolcache cleanup review feedback</li>
<li><a
href="https://github.com/github/codeql-action/commit/deece8f852f048bc3f52fd42c9cc7a99b1ebb252"><code>deece8f</code></a>
Apply suggestion from <a
href="https://github.com/henrymercer"><code>@​henrymercer</code></a></li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/cdf488f595d80d6e07e03d4674febd5ab45fa938...b96794f015dfd88f77b49b1c93e0fa7110f94c63">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-19 23:19:16 -04:00
dependabot[bot] d09ec9de25 build(deps): bump the npm-deps group in /src/frontend with 9 updates (#1349)
Bumps the npm-deps group in /src/frontend with 9 updates:

| Package | From | To |
| --- | --- | --- |
| [react](https://github.com/react/react/tree/HEAD/packages/react) |
`19.2.8` | `19.3.0` |
|
[@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react)
| `19.2.18` | `19.3.0` |
|
[react-dom](https://github.com/react/react/tree/HEAD/packages/react-dom)
| `19.2.8` | `19.3.0` |
|
[@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom)
| `19.2.7` | `19.3.0` |
|
[@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)
| `26.5.0` | `26.5.1` |
| [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) |
`6.34.0` | `6.35.1` |
| [oxfmt](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt) |
`0.67.0` | `0.68.0` |
| [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) |
`1.82.0` | `1.83.0` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) |
`8.2.2` | `8.3.0` |

Updates `react` from 19.2.8 to 19.3.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/react/react/releases">react's
releases</a>.</em></p>
<blockquote>
<h2>19.3.0 (September 9, 2026)</h2>
<p>Below is a list of all new features, APIs, and bug fixes.</p>
<p>Read the <a href="https://react.dev/blog/2026/09/09/react-19-3">React
19.3 release post</a> for more information.</p>
<h2>New React Features</h2>
<ul>
<li><code>&lt;ViewTransition /&gt;</code>: Adds <code>&lt;ViewTransition
/&gt;</code> and <code>addTransitionType</code> APIs to power View
Transition animations in React (<a
href="https://github.com/sebmarkbage"><code>@​sebmarkbage</code></a>, <a
href="https://github.com/jackpope"><code>@​jackpope</code></a>, <a
href="https://github.com/gaearon"><code>@​gaearon</code></a>: <a
href="https://redirect.github.com/facebook/react/pull/31975">#31975</a>,
<a
href="https://redirect.github.com/facebook/react/pull/31987">#31987</a>,
<a
href="https://redirect.github.com/facebook/react/pull/31996">#31996</a>,
<a
href="https://redirect.github.com/facebook/react/pull/31999">#31999</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32001">#32001</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32002">#32002</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32028">#32028</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32029">#32029</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32031">#32031</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32034">#32034</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32038">#32038</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32041">#32041</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32050">#32050</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32090">#32090</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32105">#32105</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32254">#32254</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32379">#32379</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32422">#32422</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32462">#32462</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32540">#32540</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32545">#32545</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32585">#32585</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32599">#32599</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32611">#32611</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32612">#32612</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32617">#32617</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32651">#32651</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32653">#32653</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32656">#32656</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32664">#32664</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32699">#32699</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32723">#32723</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32734">#32734</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32751">#32751</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32752">#32752</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32760">#32760</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32761">#32761</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32764">#32764</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32772">#32772</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32790">#32790</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32819">#32819</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32820">#32820</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32822">#32822</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32833">#32833</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32849">#32849</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33094">#33094</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33191">#33191</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33200">#33200</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33206">#33206</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33293">#33293</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33330">#33330</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33331">#33331</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33332">#33332</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33357">#33357</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33362">#33362</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33433">#33433</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33576">#33576</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34374">#34374</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34450">#34450</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34481">#34481</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34500">#34500</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34502">#34502</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34510">#34510</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34511">#34511</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34539">#34539</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35567">#35567</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35564">#35564</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35485">#35485</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35380">#35380</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35063">#35063</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35060">#35060</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34676">#34676</a>,
<a
href="https://redirect.github.com/facebook/react/pull/36917">#36917</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35337">#35337</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35520">#35520</a>)</li>
<li>Fragment Refs: Add Refs to <code>&lt;Fragment /&gt;</code> to
support composable platform behavior (<a
href="https://github.com/jackpope"><code>@​jackpope</code></a>, <a
href="https://github.com/sebmarkbage"><code>@​sebmarkbage</code></a>, <a
href="https://github.com/eps1lon"><code>@​eps1lon</code></a>, <a
href="https://github.com/Dhakshin2007"><code>@​Dhakshin2007</code></a>,
<a href="https://github.com/chirokas"><code>@​chirokas</code></a>, <a
href="https://github.com/teamleaderleo"><code>@​teamleaderleo</code></a>,
<a
href="https://github.com/fallintoplace"><code>@​fallintoplace</code></a>:
<a
href="https://redirect.github.com/facebook/react/pull/32465">#32465</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32613">#32613</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32619">#32619</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32654">#32654</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32660">#32660</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32682">#32682</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32722">#32722</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32813">#32813</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32814">#32814</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33056">#33056</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33058">#33058</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33093">#33093</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34069">#34069</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34103">#34103</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34544">#34544</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34545">#34545</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37062">#37062</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37061">#37061</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37060">#37060</a>,
<a
href="https://redirect.github.com/facebook/react/pull/36047">#36047</a>,
<a
href="https://redirect.github.com/facebook/react/pull/36010">#36010</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35642">#35642</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35641">#35641</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35637">#35637</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35630">#35630</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34935">#34935</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37457">#37457</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37408">#37408</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37326">#37326</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37251">#37251</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37171">#37171</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37169">#37169</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37168">#37168</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37167">#37167</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37166">#37166</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37165">#37165</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37164">#37164</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37163">#37163</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37162">#37162</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37161">#37161</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37160">#37160</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37125">#37125</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37063">#37063</a>)</li>
</ul>
<h2>New React DOM Features</h2>
<ul>
<li><code>browser()</code>: a new <code>react-dom</code> API that
returns a usable which errors during server rendering and resolves in
the browser. <code>use(browser())</code> inside a
<code>&lt;Suspense&gt;</code> boundary marks a subtree as browser-only
without reporting a recoverable error (<a
href="https://github.com/gnoff"><code>@​gnoff</code></a>: <a
href="https://redirect.github.com/facebook/react/pull/37143">#37143</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37241">#37241</a>)
<ul>
<li>Added an <code>onBrowserBailout</code> option to the
<code>react-dom/server</code> APIs to observe when a subtree defers to
the browser (<a href="https://github.com/gnoff"><code>@​gnoff</code></a>
<a
href="https://redirect.github.com/facebook/react/pull/37193">#37193</a>)</li>
</ul>
</li>
</ul>
<h2>Notable changes</h2>
<ul>
<li>Enable Trusted Types API integration (<a
href="https://github.com/rickhanlonii"><code>@​rickhanlonii</code></a>
<a
href="https://redirect.github.com/facebook/react/pull/35816">#35816</a>)</li>
<li>Transitions now render independently instead of being entangled into
a single render, so a slow transition no longer holds up unrelated ones
(<a href="https://github.com/acdlite"><code>@​acdlite</code></a> <a
href="https://redirect.github.com/facebook/react/pull/37290">#37290</a>)</li>
<li>Added a DEV-only warning when a component appears to have been
unblocked by calling <code>use()</code> conditionally (<a
href="https://github.com/hoxyq"><code>@​hoxyq</code></a>, <a
href="https://github.com/eps1lon"><code>@​eps1lon</code></a>: <a
href="https://redirect.github.com/facebook/react/pull/37104">#37104</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37203">#37203</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37491">#37491</a>)</li>
</ul>
<h2>All Changes</h2>
<h3>React</h3>
<ul>
<li>Fast Refresh Fixes
<ul>
<li>Fix Fast Refresh to find and remount edits to components wrapped
behind <code>lazy()</code> (<a
href="https://github.com/sophiebits"><code>@​sophiebits</code></a> <a
href="https://redirect.github.com/facebook/react/pull/36965">#36965</a>)</li>
<li>Fix Fast Refresh so edits to a <code>memo()</code> comparison
function take effect (<a
href="https://github.com/sophiebits"><code>@​sophiebits</code></a> <a
href="https://redirect.github.com/facebook/react/pull/36964">#36964</a>)</li>
<li>Fix Fast Refresh crash when an edit changes the kind of a
component's type (<a
href="https://github.com/sophiebits"><code>@​sophiebits</code></a> <a
href="https://redirect.github.com/facebook/react/pull/36963">#36963</a>)</li>
<li>Unify hot reload type resolution for Fast Refresh (<a
href="https://github.com/sophiebits"><code>@​sophiebits</code></a> <a
href="https://redirect.github.com/facebook/react/pull/36962">#36962</a>)</li>
<li>Fix Fast Refresh to remount correctly when an edit changes the
component kind (<a
href="https://github.com/sophiebits"><code>@​sophiebits</code></a> <a
href="https://redirect.github.com/facebook/react/pull/36950">#36950</a>)</li>
<li>Double invoke effects in StrictMode after Fast Refresh (<a
href="https://github.com/eps1lon"><code>@​eps1lon</code></a> <a
href="https://redirect.github.com/facebook/react/pull/35962">#35962</a>)</li>
</ul>
</li>
<li>Performance Track Fixes
<ul>
<li>Prevent crash when accessing <code>$$typeof</code> in Performance
Tracks (<a href="https://github.com/eps1lon"><code>@​eps1lon</code></a>
<a
href="https://redirect.github.com/facebook/react/pull/35679">#35679</a>)</li>
<li>Handle non-string function names in Performance Tracks (<a
href="https://github.com/eps1lon"><code>@​eps1lon</code></a> <a
href="https://redirect.github.com/facebook/react/pull/35659">#35659</a>)</li>
<li>Use minus (<code>-</code>) instead of en dash for removed props in
Performance Tracks (<a
href="https://github.com/eps1lon"><code>@​eps1lon</code></a> <a
href="https://redirect.github.com/facebook/react/pull/35649">#35649</a>)</li>
<li>Handle arrays with bigints in deep objects in Performance Tracks (<a
href="https://github.com/eps1lon"><code>@​eps1lon</code></a> <a
href="https://redirect.github.com/facebook/react/pull/35648">#35648</a>)</li>
<li>Don't enumerate typed array props in Performance Tracks in DEV (<a
href="https://github.com/UditDewan"><code>@​UditDewan</code></a> <a
href="https://redirect.github.com/facebook/react/pull/36913">#36913</a>)</li>
<li>Bail out of diffing wide objects and arrays in Performance Tracks
(<a href="https://github.com/eps1lon"><code>@​eps1lon</code></a> <a
href="https://redirect.github.com/facebook/react/pull/34742">#34742</a>)</li>
<li>Clear potentially large performance measures in DEV (<a
href="https://github.com/hoxyq"><code>@​hoxyq</code></a> <a
href="https://redirect.github.com/facebook/react/pull/34803">#34803</a>)</li>
<li>Fix missing else branch for renders with no props change in
Performance Tracks (<a
href="https://github.com/hoxyq"><code>@​hoxyq</code></a> <a
href="https://redirect.github.com/facebook/react/pull/34837">#34837</a>)</li>
</ul>
</li>
<li>Activity Fixes
<ul>
<li>Fix <code>useSyncExternalStore</code> missing store mutations that
happened while an Activity tree was hidden (<a
href="https://github.com/sophiebits"><code>@​sophiebits</code></a> <a
href="https://redirect.github.com/facebook/react/pull/36947">#36947</a>)</li>
<li>Hide portal contents when an Activity is hidden (<a
href="https://github.com/acdlite"><code>@​acdlite</code></a> <a
href="https://redirect.github.com/facebook/react/pull/35091">#35091</a>)</li>
<li>Prevent metadata hoisting in hidden <code>&lt;Activity&gt;</code>
trees (<a
href="https://github.com/ronnakamoto"><code>@​ronnakamoto</code></a> <a
href="https://redirect.github.com/facebook/react/pull/34983">#34983</a>)</li>
<li>Prevent errors thrown inside a hidden Activity from escaping to the
visible UI (<a
href="https://github.com/acdlite"><code>@​acdlite</code></a> <a
href="https://redirect.github.com/facebook/react/pull/35074">#35074</a>)</li>
<li>Don't unhide a node if a direct parent Offscreen is still hidden (<a
href="https://github.com/sebmarkbage"><code>@​sebmarkbage</code></a> <a
href="https://redirect.github.com/facebook/react/pull/34821">#34821</a>)</li>
<li>Don't show internal <code>&lt;Offscreen&gt;</code> component in
error messages (<a
href="https://github.com/rickhanlonii"><code>@​rickhanlonii</code></a>
<a
href="https://redirect.github.com/facebook/react/pull/35763">#35763</a>)</li>
</ul>
</li>
<li>Warn in DEV when a component appears to have been unblocked by a
conditional <code>use()</code> (<a
href="https://github.com/hoxyq"><code>@​hoxyq</code></a>, <a
href="https://github.com/eps1lon"><code>@​eps1lon</code></a>: <a
href="https://redirect.github.com/facebook/react/pull/37104">#37104</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37203">#37203</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37491">#37491</a>)</li>
<li>Render transitions independently instead of entangling them into a
single render (<a
href="https://github.com/acdlite"><code>@​acdlite</code></a> <a
href="https://redirect.github.com/facebook/react/pull/37290">#37290</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/react/react/blob/main/CHANGELOG.md">react's
changelog</a>.</em></p>
<blockquote>
<h2>19.3.0 (September 9, 2026)</h2>
<h3>New React Features</h3>
<ul>
<li><code>&lt;ViewTransition /&gt;</code>: Adds <code>&lt;ViewTransition
/&gt;</code> and <code>addTransitionType</code> APIs to power View
Transition animations in React (<a
href="https://github.com/sebmarkbage"><code>@​sebmarkbage</code></a>, <a
href="https://github.com/jackpope"><code>@​jackpope</code></a>, <a
href="https://github.com/gaearon"><code>@​gaearon</code></a>: <a
href="https://redirect.github.com/facebook/react/pull/31975">#31975</a>,
<a
href="https://redirect.github.com/facebook/react/pull/31987">#31987</a>,
<a
href="https://redirect.github.com/facebook/react/pull/31996">#31996</a>,
<a
href="https://redirect.github.com/facebook/react/pull/31999">#31999</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32001">#32001</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32002">#32002</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32028">#32028</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32029">#32029</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32031">#32031</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32034">#32034</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32038">#32038</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32041">#32041</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32050">#32050</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32090">#32090</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32105">#32105</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32254">#32254</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32379">#32379</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32422">#32422</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32462">#32462</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32540">#32540</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32545">#32545</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32585">#32585</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32599">#32599</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32611">#32611</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32612">#32612</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32617">#32617</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32651">#32651</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32653">#32653</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32656">#32656</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32664">#32664</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32699">#32699</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32723">#32723</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32734">#32734</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32751">#32751</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32752">#32752</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32760">#32760</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32761">#32761</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32764">#32764</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32772">#32772</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32790">#32790</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32819">#32819</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32820">#32820</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32822">#32822</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32833">#32833</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32849">#32849</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33094">#33094</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33191">#33191</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33200">#33200</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33206">#33206</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33293">#33293</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33330">#33330</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33331">#33331</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33332">#33332</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33357">#33357</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33362">#33362</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33433">#33433</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33576">#33576</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34374">#34374</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34450">#34450</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34481">#34481</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34500">#34500</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34502">#34502</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34510">#34510</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34511">#34511</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34539">#34539</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35567">#35567</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35564">#35564</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35485">#35485</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35380">#35380</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35063">#35063</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35060">#35060</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34676">#34676</a>,
<a
href="https://redirect.github.com/facebook/react/pull/36917">#36917</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35337">#35337</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35520">#35520</a>)</li>
<li>Fragment Refs: Add Refs to <code>&lt;Fragment /&gt;</code> to
support composable platform behavior (<a
href="https://github.com/jackpope"><code>@​jackpope</code></a>, <a
href="https://github.com/sebmarkbage"><code>@​sebmarkbage</code></a>, <a
href="https://github.com/eps1lon"><code>@​eps1lon</code></a>, <a
href="https://github.com/Dhakshin2007"><code>@​Dhakshin2007</code></a>,
<a href="https://github.com/chirokas"><code>@​chirokas</code></a>, <a
href="https://github.com/teamleaderleo"><code>@​teamleaderleo</code></a>,
<a
href="https://github.com/fallintoplace"><code>@​fallintoplace</code></a>:
<a
href="https://redirect.github.com/facebook/react/pull/32465">#32465</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32613">#32613</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32619">#32619</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32654">#32654</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32660">#32660</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32682">#32682</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32722">#32722</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32813">#32813</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32814">#32814</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33056">#33056</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33058">#33058</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33093">#33093</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34069">#34069</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34103">#34103</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34544">#34544</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34545">#34545</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37062">#37062</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37061">#37061</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37060">#37060</a>,
<a
href="https://redirect.github.com/facebook/react/pull/36047">#36047</a>,
<a
href="https://redirect.github.com/facebook/react/pull/36010">#36010</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35642">#35642</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35641">#35641</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35637">#35637</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35630">#35630</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34935">#34935</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37457">#37457</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37408">#37408</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37326">#37326</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37251">#37251</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37171">#37171</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37169">#37169</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37168">#37168</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37167">#37167</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37166">#37166</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37165">#37165</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37164">#37164</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37163">#37163</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37162">#37162</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37161">#37161</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37160">#37160</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37125">#37125</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37063">#37063</a>)</li>
</ul>
<h3>New React DOM Features</h3>
<ul>
<li><code>browser()</code>: a new <code>react-dom</code> API that
returns a usable which errors during server rendering and resolves in
the browser. <code>use(browser())</code> inside a
<code>&lt;Suspense&gt;</code> boundary marks a subtree as browser-only
without reporting a recoverable error (<a
href="https://github.com/gnoff"><code>@​gnoff</code></a>: <a
href="https://redirect.github.com/facebook/react/pull/37143">#37143</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37241">#37241</a>)
<ul>
<li>Added an <code>onBrowserBailout</code> option to the
<code>react-dom/server</code> APIs to observe when a subtree defers to
the browser (<a href="https://github.com/gnoff"><code>@​gnoff</code></a>
<a
href="https://redirect.github.com/facebook/react/pull/37193">#37193</a>)</li>
</ul>
</li>
</ul>
<h3>Notable changes</h3>
<ul>
<li>Enable Trusted Types API integration (<a
href="https://github.com/rickhanlonii"><code>@​rickhanlonii</code></a>
<a
href="https://redirect.github.com/facebook/react/pull/35816">#35816</a>)</li>
<li>Transitions now render independently instead of being entangled into
a single render, so a slow transition no longer holds up unrelated ones
(<a href="https://github.com/acdlite"><code>@​acdlite</code></a> <a
href="https://redirect.github.com/facebook/react/pull/37290">#37290</a>)</li>
<li>Added a DEV-only warning when a component appears to have been
unblocked by calling <code>use()</code> conditionally (<a
href="https://github.com/hoxyq"><code>@​hoxyq</code></a>, <a
href="https://github.com/eps1lon"><code>@​eps1lon</code></a>: <a
href="https://redirect.github.com/facebook/react/pull/37104">#37104</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37203">#37203</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37491">#37491</a>)</li>
</ul>
<h3>All Changes</h3>
<h4>React</h4>
<ul>
<li>Fast Refresh Fixes
<ul>
<li>Fix Fast Refresh to find and remount edits to components wrapped
behind <code>lazy()</code> (<a
href="https://github.com/sophiebits"><code>@​sophiebits</code></a> <a
href="https://redirect.github.com/facebook/react/pull/36965">#36965</a>)</li>
<li>Fix Fast Refresh so edits to a <code>memo()</code> comparison
function take effect (<a
href="https://github.com/sophiebits"><code>@​sophiebits</code></a> <a
href="https://redirect.github.com/facebook/react/pull/36964">#36964</a>)</li>
<li>Fix Fast Refresh crash when an edit changes the kind of a
component's type (<a
href="https://github.com/sophiebits"><code>@​sophiebits</code></a> <a
href="https://redirect.github.com/facebook/react/pull/36963">#36963</a>)</li>
<li>Unify hot reload type resolution for Fast Refresh (<a
href="https://github.com/sophiebits"><code>@​sophiebits</code></a> <a
href="https://redirect.github.com/facebook/react/pull/36962">#36962</a>)</li>
<li>Fix Fast Refresh to remount correctly when an edit changes the
component kind (<a
href="https://github.com/sophiebits"><code>@​sophiebits</code></a> <a
href="https://redirect.github.com/facebook/react/pull/36950">#36950</a>)</li>
<li>Double invoke effects in StrictMode after Fast Refresh (<a
href="https://github.com/eps1lon"><code>@​eps1lon</code></a> <a
href="https://redirect.github.com/facebook/react/pull/35962">#35962</a>)</li>
</ul>
</li>
<li>Performance Track Fixes
<ul>
<li>Prevent crash when accessing <code>$$typeof</code> in Performance
Tracks (<a href="https://github.com/eps1lon"><code>@​eps1lon</code></a>
<a
href="https://redirect.github.com/facebook/react/pull/35679">#35679</a>)</li>
<li>Handle non-string function names in Performance Tracks (<a
href="https://github.com/eps1lon"><code>@​eps1lon</code></a> <a
href="https://redirect.github.com/facebook/react/pull/35659">#35659</a>)</li>
<li>Use minus (<code>-</code>) instead of en dash for removed props in
Performance Tracks (<a
href="https://github.com/eps1lon"><code>@​eps1lon</code></a> <a
href="https://redirect.github.com/facebook/react/pull/35649">#35649</a>)</li>
<li>Handle arrays with bigints in deep objects in Performance Tracks (<a
href="https://github.com/eps1lon"><code>@​eps1lon</code></a> <a
href="https://redirect.github.com/facebook/react/pull/35648">#35648</a>)</li>
<li>Don't enumerate typed array props in Performance Tracks in DEV (<a
href="https://github.com/UditDewan"><code>@​UditDewan</code></a> <a
href="https://redirect.github.com/facebook/react/pull/36913">#36913</a>)</li>
<li>Bail out of diffing wide objects and arrays in Performance Tracks
(<a href="https://github.com/eps1lon"><code>@​eps1lon</code></a> <a
href="https://redirect.github.com/facebook/react/pull/34742">#34742</a>)</li>
<li>Clear potentially large performance measures in DEV (<a
href="https://github.com/hoxyq"><code>@​hoxyq</code></a> <a
href="https://redirect.github.com/facebook/react/pull/34803">#34803</a>)</li>
<li>Fix missing else branch for renders with no props change in
Performance Tracks (<a
href="https://github.com/hoxyq"><code>@​hoxyq</code></a> <a
href="https://redirect.github.com/facebook/react/pull/34837">#34837</a>)</li>
</ul>
</li>
<li>Activity Fixes
<ul>
<li>Fix <code>useSyncExternalStore</code> missing store mutations that
happened while an Activity tree was hidden (<a
href="https://github.com/sophiebits"><code>@​sophiebits</code></a> <a
href="https://redirect.github.com/facebook/react/pull/36947">#36947</a>)</li>
<li>Hide portal contents when an Activity is hidden (<a
href="https://github.com/acdlite"><code>@​acdlite</code></a> <a
href="https://redirect.github.com/facebook/react/pull/35091">#35091</a>)</li>
<li>Prevent metadata hoisting in hidden <code>&lt;Activity&gt;</code>
trees (<a
href="https://github.com/ronnakamoto"><code>@​ronnakamoto</code></a> <a
href="https://redirect.github.com/facebook/react/pull/34983">#34983</a>)</li>
<li>Prevent errors thrown inside a hidden Activity from escaping to the
visible UI (<a
href="https://github.com/acdlite"><code>@​acdlite</code></a> <a
href="https://redirect.github.com/facebook/react/pull/35074">#35074</a>)</li>
<li>Don't unhide a node if a direct parent Offscreen is still hidden (<a
href="https://github.com/sebmarkbage"><code>@​sebmarkbage</code></a> <a
href="https://redirect.github.com/facebook/react/pull/34821">#34821</a>)</li>
<li>Don't show internal <code>&lt;Offscreen&gt;</code> component in
error messages (<a
href="https://github.com/rickhanlonii"><code>@​rickhanlonii</code></a>
<a
href="https://redirect.github.com/facebook/react/pull/35763">#35763</a>)</li>
</ul>
</li>
<li>Warn in DEV when a component appears to have been unblocked by a
conditional <code>use()</code> (<a
href="https://github.com/hoxyq"><code>@​hoxyq</code></a>, <a
href="https://github.com/eps1lon"><code>@​eps1lon</code></a>: <a
href="https://redirect.github.com/facebook/react/pull/37104">#37104</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37203">#37203</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37491">#37491</a>)</li>
<li>Render transitions independently instead of entangling them into a
single render (<a
href="https://github.com/acdlite"><code>@​acdlite</code></a> <a
href="https://redirect.github.com/facebook/react/pull/37290">#37290</a>)</li>
<li>Fix hang when updating a dehydrated boundary inside a hidden tree
(<a href="https://github.com/gaearon"><code>@​gaearon</code></a> <a
href="https://redirect.github.com/facebook/react/pull/37135">#37135</a>)</li>
<li>Don't reacquire Host Singletons during dev effect validation (<a
href="https://github.com/gnoff"><code>@​gnoff</code></a> <a
href="https://redirect.github.com/facebook/react/pull/37113">#37113</a>)</li>
<li>Only remove properties from Host Singletons on release (<a
href="https://github.com/gnoff"><code>@​gnoff</code></a> <a
href="https://redirect.github.com/facebook/react/pull/37112">#37112</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/react/react/commit/2dc7da790d6388b95b83198ca9b588b2ad5f5c0b"><code>2dc7da7</code></a>
[test] Bump Jest to 30.4 (<a
href="https://github.com/react/react/tree/HEAD/packages/react/issues/37382">#37382</a>)</li>
<li><a
href="https://github.com/react/react/commit/4f9389423b7319e1f7acc3d158c84a8365462748"><code>4f93894</code></a>
docs: remove stale parentType param from validateChildKeys JSDoc (<a
href="https://github.com/react/react/tree/HEAD/packages/react/issues/36928">#36928</a>)</li>
<li><a
href="https://github.com/react/react/commit/dbc37501ffeaf8fec45af5898caf1c3d64ad10bf"><code>dbc3750</code></a>
Update required references to GitHub repo (<a
href="https://github.com/react/react/tree/HEAD/packages/react/issues/36752">#36752</a>)</li>
<li><a
href="https://github.com/react/react/commit/900ae094d85b11c67d53dd14af50a2bda5db4495"><code>900ae09</code></a>
[flow] Bump flow to v0.317.0 (<a
href="https://github.com/react/react/tree/HEAD/packages/react/issues/36701">#36701</a>)</li>
<li><a
href="https://github.com/react/react/commit/fbb137059e4aacfaab1d36516e9b55050b4a0454"><code>fbb1370</code></a>
[flow] Bump flow to v0.307.1 (<a
href="https://github.com/react/react/tree/HEAD/packages/react/issues/36199">#36199</a>)</li>
<li><a
href="https://github.com/react/react/commit/56922cf751fab6c7ab4c12ddbbd15839959fa255"><code>56922cf</code></a>
[react-native-renderer] Delete Paper (legacy) renderer (<a
href="https://github.com/react/react/tree/HEAD/packages/react/issues/36285">#36285</a>)</li>
<li><a
href="https://github.com/react/react/commit/74568e8627aa43469b74f2972f427a209639d0b6"><code>74568e8</code></a>
[Flight] Transport <code>AggregateErrors.errors</code> (<a
href="https://github.com/react/react/tree/HEAD/packages/react/issues/36156">#36156</a>)</li>
<li><a
href="https://github.com/react/react/commit/e66ef6480ecd19c6885f2c06dec34fec1fdc0a98"><code>e66ef64</code></a>
[tests] remove withoutStack from assertConsole helpers (<a
href="https://github.com/react/react/tree/HEAD/packages/react/issues/35498">#35498</a>)</li>
<li><a
href="https://github.com/react/react/commit/db71391c5c70dc113560d1c23d0b6548604d827f"><code>db71391</code></a>
[Fiber] Instrument the lazy initializer thenable in all cases (<a
href="https://github.com/react/react/tree/HEAD/packages/react/issues/35521">#35521</a>)</li>
<li><a
href="https://github.com/react/react/commit/3e1abcc8d7083a13adf4774feb0d67ecbe4a2bc4"><code>3e1abcc</code></a>
[tests] Require exact error messages in assertConsole helpers (<a
href="https://github.com/react/react/tree/HEAD/packages/react/issues/35497">#35497</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/react/react/commits/v19.3.0/packages/react">compare
view</a></li>
</ul>
</details>
<br />

Updates `@types/react` from 19.2.18 to 19.3.0
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react">compare
view</a></li>
</ul>
</details>
<br />

Updates `react-dom` from 19.2.8 to 19.3.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/react/react/releases">react-dom's
releases</a>.</em></p>
<blockquote>
<h2>19.3.0 (September 9, 2026)</h2>
<p>Below is a list of all new features, APIs, and bug fixes.</p>
<p>Read the <a href="https://react.dev/blog/2026/09/09/react-19-3">React
19.3 release post</a> for more information.</p>
<h2>New React Features</h2>
<ul>
<li><code>&lt;ViewTransition /&gt;</code>: Adds <code>&lt;ViewTransition
/&gt;</code> and <code>addTransitionType</code> APIs to power View
Transition animations in React (<a
href="https://github.com/sebmarkbage"><code>@​sebmarkbage</code></a>, <a
href="https://github.com/jackpope"><code>@​jackpope</code></a>, <a
href="https://github.com/gaearon"><code>@​gaearon</code></a>: <a
href="https://redirect.github.com/facebook/react/pull/31975">#31975</a>,
<a
href="https://redirect.github.com/facebook/react/pull/31987">#31987</a>,
<a
href="https://redirect.github.com/facebook/react/pull/31996">#31996</a>,
<a
href="https://redirect.github.com/facebook/react/pull/31999">#31999</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32001">#32001</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32002">#32002</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32028">#32028</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32029">#32029</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32031">#32031</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32034">#32034</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32038">#32038</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32041">#32041</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32050">#32050</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32090">#32090</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32105">#32105</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32254">#32254</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32379">#32379</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32422">#32422</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32462">#32462</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32540">#32540</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32545">#32545</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32585">#32585</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32599">#32599</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32611">#32611</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32612">#32612</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32617">#32617</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32651">#32651</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32653">#32653</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32656">#32656</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32664">#32664</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32699">#32699</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32723">#32723</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32734">#32734</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32751">#32751</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32752">#32752</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32760">#32760</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32761">#32761</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32764">#32764</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32772">#32772</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32790">#32790</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32819">#32819</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32820">#32820</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32822">#32822</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32833">#32833</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32849">#32849</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33094">#33094</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33191">#33191</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33200">#33200</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33206">#33206</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33293">#33293</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33330">#33330</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33331">#33331</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33332">#33332</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33357">#33357</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33362">#33362</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33433">#33433</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33576">#33576</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34374">#34374</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34450">#34450</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34481">#34481</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34500">#34500</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34502">#34502</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34510">#34510</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34511">#34511</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34539">#34539</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35567">#35567</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35564">#35564</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35485">#35485</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35380">#35380</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35063">#35063</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35060">#35060</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34676">#34676</a>,
<a
href="https://redirect.github.com/facebook/react/pull/36917">#36917</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35337">#35337</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35520">#35520</a>)</li>
<li>Fragment Refs: Add Refs to <code>&lt;Fragment /&gt;</code> to
support composable platform behavior (<a
href="https://github.com/jackpope"><code>@​jackpope</code></a>, <a
href="https://github.com/sebmarkbage"><code>@​sebmarkbage</code></a>, <a
href="https://github.com/eps1lon"><code>@​eps1lon</code></a>, <a
href="https://github.com/Dhakshin2007"><code>@​Dhakshin2007</code></a>,
<a href="https://github.com/chirokas"><code>@​chirokas</code></a>, <a
href="https://github.com/teamleaderleo"><code>@​teamleaderleo</code></a>,
<a
href="https://github.com/fallintoplace"><code>@​fallintoplace</code></a>:
<a
href="https://redirect.github.com/facebook/react/pull/32465">#32465</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32613">#32613</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32619">#32619</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32654">#32654</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32660">#32660</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32682">#32682</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32722">#32722</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32813">#32813</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32814">#32814</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33056">#33056</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33058">#33058</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33093">#33093</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34069">#34069</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34103">#34103</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34544">#34544</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34545">#34545</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37062">#37062</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37061">#37061</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37060">#37060</a>,
<a
href="https://redirect.github.com/facebook/react/pull/36047">#36047</a>,
<a
href="https://redirect.github.com/facebook/react/pull/36010">#36010</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35642">#35642</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35641">#35641</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35637">#35637</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35630">#35630</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34935">#34935</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37457">#37457</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37408">#37408</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37326">#37326</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37251">#37251</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37171">#37171</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37169">#37169</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37168">#37168</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37167">#37167</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37166">#37166</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37165">#37165</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37164">#37164</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37163">#37163</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37162">#37162</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37161">#37161</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37160">#37160</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37125">#37125</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37063">#37063</a>)</li>
</ul>
<h2>New React DOM Features</h2>
<ul>
<li><code>browser()</code>: a new <code>react-dom</code> API that
returns a usable which errors during server rendering and resolves in
the browser. <code>use(browser())</code> inside a
<code>&lt;Suspense&gt;</code> boundary marks a subtree as browser-only
without reporting a recoverable error (<a
href="https://github.com/gnoff"><code>@​gnoff</code></a>: <a
href="https://redirect.github.com/facebook/react/pull/37143">#37143</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37241">#37241</a>)
<ul>
<li>Added an <code>onBrowserBailout</code> option to the
<code>react-dom/server</code> APIs to observe when a subtree defers to
the browser (<a href="https://github.com/gnoff"><code>@​gnoff</code></a>
<a
href="https://redirect.github.com/facebook/react/pull/37193">#37193</a>)</li>
</ul>
</li>
</ul>
<h2>Notable changes</h2>
<ul>
<li>Enable Trusted Types API integration (<a
href="https://github.com/rickhanlonii"><code>@​rickhanlonii</code></a>
<a
href="https://redirect.github.com/facebook/react/pull/35816">#35816</a>)</li>
<li>Transitions now render independently instead of being entangled into
a single render, so a slow transition no longer holds up unrelated ones
(<a href="https://github.com/acdlite"><code>@​acdlite</code></a> <a
href="https://redirect.github.com/facebook/react/pull/37290">#37290</a>)</li>
<li>Added a DEV-only warning when a component appears to have been
unblocked by calling <code>use()</code> conditionally (<a
href="https://github.com/hoxyq"><code>@​hoxyq</code></a>, <a
href="https://github.com/eps1lon"><code>@​eps1lon</code></a>: <a
href="https://redirect.github.com/facebook/react/pull/37104">#37104</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37203">#37203</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37491">#37491</a>)</li>
</ul>
<h2>All Changes</h2>
<h3>React</h3>
<ul>
<li>Fast Refresh Fixes
<ul>
<li>Fix Fast Refresh to find and remount edits to components wrapped
behind <code>lazy()</code> (<a
href="https://github.com/sophiebits"><code>@​sophiebits</code></a> <a
href="https://redirect.github.com/facebook/react/pull/36965">#36965</a>)</li>
<li>Fix Fast Refresh so edits to a <code>memo()</code> comparison
function take effect (<a
href="https://github.com/sophiebits"><code>@​sophiebits</code></a> <a
href="https://redirect.github.com/facebook/react/pull/36964">#36964</a>)</li>
<li>Fix Fast Refresh crash when an edit changes the kind of a
component's type (<a
href="https://github.com/sophiebits"><code>@​sophiebits</code></a> <a
href="https://redirect.github.com/facebook/react/pull/36963">#36963</a>)</li>
<li>Unify hot reload type resolution for Fast Refresh (<a
href="https://github.com/sophiebits"><code>@​sophiebits</code></a> <a
href="https://redirect.github.com/facebook/react/pull/36962">#36962</a>)</li>
<li>Fix Fast Refresh to remount correctly when an edit changes the
component kind (<a
href="https://github.com/sophiebits"><code>@​sophiebits</code></a> <a
href="https://redirect.github.com/facebook/react/pull/36950">#36950</a>)</li>
<li>Double invoke effects in StrictMode after Fast Refresh (<a
href="https://github.com/eps1lon"><code>@​eps1lon</code></a> <a
href="https://redirect.github.com/facebook/react/pull/35962">#35962</a>)</li>
</ul>
</li>
<li>Performance Track Fixes
<ul>
<li>Prevent crash when accessing <code>$$typeof</code> in Performance
Tracks (<a href="https://github.com/eps1lon"><code>@​eps1lon</code></a>
<a
href="https://redirect.github.com/facebook/react/pull/35679">#35679</a>)</li>
<li>Handle non-string function names in Performance Tracks (<a
href="https://github.com/eps1lon"><code>@​eps1lon</code></a> <a
href="https://redirect.github.com/facebook/react/pull/35659">#35659</a>)</li>
<li>Use minus (<code>-</code>) instead of en dash for removed props in
Performance Tracks (<a
href="https://github.com/eps1lon"><code>@​eps1lon</code></a> <a
href="https://redirect.github.com/facebook/react/pull/35649">#35649</a>)</li>
<li>Handle arrays with bigints in deep objects in Performance Tracks (<a
href="https://github.com/eps1lon"><code>@​eps1lon</code></a> <a
href="https://redirect.github.com/facebook/react/pull/35648">#35648</a>)</li>
<li>Don't enumerate typed array props in Performance Tracks in DEV (<a
href="https://github.com/UditDewan"><code>@​UditDewan</code></a> <a
href="https://redirect.github.com/facebook/react/pull/36913">#36913</a>)</li>
<li>Bail out of diffing wide objects and arrays in Performance Tracks
(<a href="https://github.com/eps1lon"><code>@​eps1lon</code></a> <a
href="https://redirect.github.com/facebook/react/pull/34742">#34742</a>)</li>
<li>Clear potentially large performance measures in DEV (<a
href="https://github.com/hoxyq"><code>@​hoxyq</code></a> <a
href="https://redirect.github.com/facebook/react/pull/34803">#34803</a>)</li>
<li>Fix missing else branch for renders with no props change in
Performance Tracks (<a
href="https://github.com/hoxyq"><code>@​hoxyq</code></a> <a
href="https://redirect.github.com/facebook/react/pull/34837">#34837</a>)</li>
</ul>
</li>
<li>Activity Fixes
<ul>
<li>Fix <code>useSyncExternalStore</code> missing store mutations that
happened while an Activity tree was hidden (<a
href="https://github.com/sophiebits"><code>@​sophiebits</code></a> <a
href="https://redirect.github.com/facebook/react/pull/36947">#36947</a>)</li>
<li>Hide portal contents when an Activity is hidden (<a
href="https://github.com/acdlite"><code>@​acdlite</code></a> <a
href="https://redirect.github.com/facebook/react/pull/35091">#35091</a>)</li>
<li>Prevent metadata hoisting in hidden <code>&lt;Activity&gt;</code>
trees (<a
href="https://github.com/ronnakamoto"><code>@​ronnakamoto</code></a> <a
href="https://redirect.github.com/facebook/react/pull/34983">#34983</a>)</li>
<li>Prevent errors thrown inside a hidden Activity from escaping to the
visible UI (<a
href="https://github.com/acdlite"><code>@​acdlite</code></a> <a
href="https://redirect.github.com/facebook/react/pull/35074">#35074</a>)</li>
<li>Don't unhide a node if a direct parent Offscreen is still hidden (<a
href="https://github.com/sebmarkbage"><code>@​sebmarkbage</code></a> <a
href="https://redirect.github.com/facebook/react/pull/34821">#34821</a>)</li>
<li>Don't show internal <code>&lt;Offscreen&gt;</code> component in
error messages (<a
href="https://github.com/rickhanlonii"><code>@​rickhanlonii</code></a>
<a
href="https://redirect.github.com/facebook/react/pull/35763">#35763</a>)</li>
</ul>
</li>
<li>Warn in DEV when a component appears to have been unblocked by a
conditional <code>use()</code> (<a
href="https://github.com/hoxyq"><code>@​hoxyq</code></a>, <a
href="https://github.com/eps1lon"><code>@​eps1lon</code></a>: <a
href="https://redirect.github.com/facebook/react/pull/37104">#37104</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37203">#37203</a>,
<a
href="https://redirect.github.com/facebook/react/pull/37491">#37491</a>)</li>
<li>Render transitions independently instead of entangling them into a
single render (<a
href="https://github.com/acdlite"><code>@​acdlite</code></a> <a
href="https://redirect.github.com/facebook/react/pull/37290">#37290</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/react/react/blob/main/CHANGELOG.md">react-dom's
changelog</a>.</em></p>
<blockquote>
<h2>19.3.0 (September 9, 2026)</h2>
<h3>New React Features</h3>
<ul>
<li><code>&lt;ViewTransition /&gt;</code>: Adds <code>&lt;ViewTransition
/&gt;</code> and <code>addTransitionType</code> APIs to power View
Transition animations in React (<a
href="https://github.com/sebmarkbage"><code>@​sebmarkbage</code></a>, <a
href="https://github.com/jackpope"><code>@​jackpope</code></a>, <a
href="https://github.com/gaearon"><code>@​gaearon</code></a>: <a
href="https://redirect.github.com/facebook/react/pull/31975">#31975</a>,
<a
href="https://redirect.github.com/facebook/react/pull/31987">#31987</a>,
<a
href="https://redirect.github.com/facebook/react/pull/31996">#31996</a>,
<a
href="https://redirect.github.com/facebook/react/pull/31999">#31999</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32001">#32001</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32002">#32002</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32028">#32028</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32029">#32029</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32031">#32031</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32034">#32034</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32038">#32038</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32041">#32041</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32050">#32050</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32090">#32090</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32105">#32105</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32254">#32254</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32379">#32379</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32422">#32422</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32462">#32462</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32540">#32540</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32545">#32545</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32585">#32585</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32599">#32599</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32611">#32611</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32612">#32612</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32617">#32617</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32651">#32651</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32653">#32653</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32656">#32656</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32664">#32664</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32699">#32699</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32723">#32723</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32734">#32734</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32751">#32751</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32752">#32752</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32760">#32760</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32761">#32761</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32764">#32764</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32772">#32772</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32790">#32790</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32819">#32819</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32820">#32820</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32822">#32822</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32833">#32833</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32849">#32849</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33094">#33094</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33191">#33191</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33200">#33200</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33206">#33206</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33293">#33293</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33330">#33330</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33331">#33331</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33332">#33332</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33357">#33357</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33362">#33362</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33433">#33433</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33576">#33576</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34374">#34374</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34450">#34450</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34481">#34481</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34500">#34500</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34502">#34502</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34510">#34510</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34511">#34511</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34539">#34539</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35567">#35567</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35564">#35564</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35485">#35485</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35380">#35380</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35063">#35063</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35060">#35060</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34676">#34676</a>,
<a
href="https://redirect.github.com/facebook/react/pull/36917">#36917</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35337">#35337</a>,
<a
href="https://redirect.github.com/facebook/react/pull/35520">#35520</a>)</li>
<li>Fragment Refs: Add Refs to <code>&lt;Fragment /&gt;</code> to
support composable platform behavior (<a
href="https://github.com/jackpope"><code>@​jackpope</code></a>, <a
href="https://github.com/sebmarkbage"><code>@​sebmarkbage</code></a>, <a
href="https://github.com/eps1lon"><code>@​eps1lon</code></a>, <a
href="https://github.com/Dhakshin2007"><code>@​Dhakshin2007</code></a>,
<a href="https://github.com/chirokas"><code>@​chirokas</code></a>, <a
href="https://github.com/teamleaderleo"><code>@​teamleaderleo</code></a>,
<a
href="https://github.com/fallintoplace"><code>@​fallintoplace</code></a>:
<a
href="https://redirect.github.com/facebook/react/pull/32465">#32465</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32613">#32613</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32619">#32619</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32654">#32654</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32660">#32660</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32682">#32682</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32722">#32722</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32813">#32813</a>,
<a
href="https://redirect.github.com/facebook/react/pull/32814">#32814</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33056">#33056</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33058">#33058</a>,
<a
href="https://redirect.github.com/facebook/react/pull/33093">#33093</a>,
<a
href="https://redirect.github.com/facebook/react/pull/34069">#34069</a>,
<a href="https://redirect...

_Description has been truncated_

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-19 23:19:07 -04:00
dependabot[bot] 83f86242b4 build(deps): bump the docker-base-image-digests group with 2 updates (#1348)
> [!WARNING]
> Cooldown could not be applied because no publication date was
available from the registry.
>

Bumps the docker-base-image-digests group with 2 updates: node and
[astral-sh/uv](https://github.com/astral-sh/uv).

Updates `node` from `50c8e8c` to `ebfe2f9`

Updates `astral-sh/uv` from 0.12.13 to 0.12.16
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/uv/releases">astral-sh/uv's
releases</a>.</em></p>
<blockquote>
<h2>0.12.16</h2>
<h2>Release Notes</h2>
<p>Released on 2026-09-17.</p>
<h3>Python</h3>
<ul>
<li>Add Pyodide 314.0.7, 0.29.5, and 0.27.8 (<a
href="https://redirect.github.com/astral-sh/uv/pull/21741">#21741</a>)</li>
</ul>
<h3>Enhancements</h3>
<ul>
<li>Verify downloaded wheels and source distributions against hashes
supplied by package indexes (<a
href="https://redirect.github.com/astral-sh/uv/pull/21562">#21562</a>)</li>
<li>Allow <code>build-constraint-dependencies</code> entries to include
hashes for verifying downloaded build dependencies (<a
href="https://redirect.github.com/astral-sh/uv/pull/21467">#21467</a>)</li>
<li>Honor Darwin <code>platform_release</code> markers in
<code>required-environments</code> using macOS wheel deployment targets
(<a
href="https://redirect.github.com/astral-sh/uv/pull/21766">#21766</a>)</li>
<li>Reject unsupported Git URL schemes while parsing lockfiles instead
of panicking during frozen exports (<a
href="https://redirect.github.com/astral-sh/uv/pull/21779">#21779</a>)</li>
</ul>
<h3>Preview features</h3>
<ul>
<li>Support <code>lock-without-metadata</code> across all dependency
types while retaining <code>package.metadata</code> for remote URL
dependencies to enable offline validation (<a
href="https://redirect.github.com/astral-sh/uv/pull/21163">#21163</a>)</li>
<li>Honor configured and command-line index settings, including
credentials, in <code>uv upgrade</code> (<a
href="https://redirect.github.com/astral-sh/uv/pull/21776">#21776</a>)</li>
<li>Allow <code>uv check</code> to run in projects that are not managed
by uv and outside workspaces (<a
href="https://redirect.github.com/astral-sh/uv/pull/21777">#21777</a>)</li>
<li>Respect <code>--python</code> and <code>UV_PYTHON</code> when
selecting the Python version for <code>uv check</code> (<a
href="https://redirect.github.com/astral-sh/uv/pull/21744">#21744</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Redact Azure shared access signatures from displayed and logged URLs
(<a
href="https://redirect.github.com/astral-sh/uv/pull/21755">#21755</a>)</li>
<li>Check archive sizes from <code>pylock.toml</code> before reusing
cached distributions (<a
href="https://redirect.github.com/astral-sh/uv/pull/21609">#21609</a>)</li>
<li>Keep user-authored local dependency paths relative in lockfiles when
backend metadata reports absolute paths (<a
href="https://redirect.github.com/astral-sh/uv/pull/20631">#20631</a>)</li>
<li>Use the bundled <code>uv_build</code> backend only when its version
matches active version pins (<a
href="https://redirect.github.com/astral-sh/uv/pull/21742">#21742</a>)</li>
<li>Handle malformed index URLs without panicking when credentials are
configured (<a
href="https://redirect.github.com/astral-sh/uv/pull/21784">#21784</a>)</li>
<li>Report a configuration error instead of panicking for proxy URLs
without a host (<a
href="https://redirect.github.com/astral-sh/uv/pull/21781">#21781</a>)</li>
<li>Return a credential-redacted error instead of panicking when a URL
cannot be converted to a path (<a
href="https://redirect.github.com/astral-sh/uv/pull/21783">#21783</a>)</li>
</ul>
<h2>Install uv 0.12.16</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://releases.astral.sh/github/uv/releases/download/0.12.16/uv-installer.sh
| sh
</code></pre>
<h3>Install prebuilt binaries via powershell script</h3>
<pre lang="sh"><code>powershell -ExecutionPolicy Bypass -c &quot;irm
https://releases.astral.sh/github/uv/releases/download/0.12.16/uv-installer.ps1
| iex&quot;
</code></pre>
<h2>Download uv 0.12.16</h2>
<p>|  File  | Platform | Checksum |</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/uv/blob/main/CHANGELOG.md">astral-sh/uv's
changelog</a>.</em></p>
<blockquote>
<h2>0.12.16</h2>
<p>Released on 2026-09-17.</p>
<h3>Python</h3>
<ul>
<li>Add Pyodide 314.0.7, 0.29.5, and 0.27.8 (<a
href="https://redirect.github.com/astral-sh/uv/pull/21741">#21741</a>)</li>
</ul>
<h3>Enhancements</h3>
<ul>
<li>Verify downloaded wheels and source distributions against hashes
supplied by package indexes (<a
href="https://redirect.github.com/astral-sh/uv/pull/21562">#21562</a>)</li>
<li>Allow <code>build-constraint-dependencies</code> entries to include
hashes for verifying downloaded build dependencies (<a
href="https://redirect.github.com/astral-sh/uv/pull/21467">#21467</a>)</li>
<li>Honor Darwin <code>platform_release</code> markers in
<code>required-environments</code> using macOS wheel deployment targets
(<a
href="https://redirect.github.com/astral-sh/uv/pull/21766">#21766</a>)</li>
<li>Reject unsupported Git URL schemes while parsing lockfiles instead
of panicking during frozen exports (<a
href="https://redirect.github.com/astral-sh/uv/pull/21779">#21779</a>)</li>
</ul>
<h3>Preview features</h3>
<ul>
<li>Support <code>lock-without-metadata</code> across all dependency
types while retaining <code>package.metadata</code> for remote URL
dependencies to enable offline validation (<a
href="https://redirect.github.com/astral-sh/uv/pull/21163">#21163</a>)</li>
<li>Honor configured and command-line index settings, including
credentials, in <code>uv upgrade</code> (<a
href="https://redirect.github.com/astral-sh/uv/pull/21776">#21776</a>)</li>
<li>Allow <code>uv check</code> to run in projects that are not managed
by uv and outside workspaces (<a
href="https://redirect.github.com/astral-sh/uv/pull/21777">#21777</a>)</li>
<li>Respect <code>--python</code> and <code>UV_PYTHON</code> when
selecting the Python version for <code>uv check</code> (<a
href="https://redirect.github.com/astral-sh/uv/pull/21744">#21744</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Redact Azure shared access signatures from displayed and logged URLs
(<a
href="https://redirect.github.com/astral-sh/uv/pull/21755">#21755</a>)</li>
<li>Check archive sizes from <code>pylock.toml</code> before reusing
cached distributions (<a
href="https://redirect.github.com/astral-sh/uv/pull/21609">#21609</a>)</li>
<li>Keep user-authored local dependency paths relative in lockfiles when
backend metadata reports absolute paths (<a
href="https://redirect.github.com/astral-sh/uv/pull/20631">#20631</a>)</li>
<li>Use the bundled <code>uv_build</code> backend only when its version
matches active version pins (<a
href="https://redirect.github.com/astral-sh/uv/pull/21742">#21742</a>)</li>
<li>Handle malformed index URLs without panicking when credentials are
configured (<a
href="https://redirect.github.com/astral-sh/uv/pull/21784">#21784</a>)</li>
<li>Report a configuration error instead of panicking for proxy URLs
without a host (<a
href="https://redirect.github.com/astral-sh/uv/pull/21781">#21781</a>)</li>
<li>Return a credential-redacted error instead of panicking when a URL
cannot be converted to a path (<a
href="https://redirect.github.com/astral-sh/uv/pull/21783">#21783</a>)</li>
</ul>
<h2>0.12.15</h2>
<p>Released on 2026-09-15.</p>
<h3>Performance</h3>
<ul>
<li>Speed up cold-cache resolution and HTTP cache revalidation by
batching cache writes (<a
href="https://redirect.github.com/astral-sh/uv/pull/21675">#21675</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Fix regressions in <code>0.12.14</code> when installing to symlinked
destinations or using <code>uv pip install --target .</code> (<a
href="https://redirect.github.com/astral-sh/uv/pull/21699">#21699</a>)</li>
</ul>
<h2>0.12.14</h2>
<p>Released on 2026-09-15.</p>
<h3>Enhancements</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/uv/commit/761ff1379b3b79f61fc8d421dfe4fe064834e084"><code>761ff13</code></a>
Bump version to 0.12.16 (<a
href="https://redirect.github.com/astral-sh/uv/issues/21809">#21809</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/a2f820ad0cebb7118f7686be3619d48df5121f6e"><code>a2f820a</code></a>
Assign release pull requests to the workflow initiator (<a
href="https://redirect.github.com/astral-sh/uv/issues/21808">#21808</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/6dffe7e03898409c04e2f46436bffa60abbae465"><code>6dffe7e</code></a>
Ignore <code>UV_NATIVE_TLS</code> when <code>UV_SYSTEM_CERTS</code> is
set (<a
href="https://redirect.github.com/astral-sh/uv/issues/21805">#21805</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/7bc36767ae3ea0d45e3cc6f219765fdec2992a33"><code>7bc3676</code></a>
Avoid warning when both <code>native-tls</code> and
<code>system-certs</code> are configured (<a
href="https://redirect.github.com/astral-sh/uv/issues/21806">#21806</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/07b838a33179967b87d513825b02504a75b21b3a"><code>07b838a</code></a>
Avoid warning when both <code>UV_NATIVE_TLS</code> and
<code>UV_SYSTEM_CERTS</code> are set (<a
href="https://redirect.github.com/astral-sh/uv/issues/21788">#21788</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/5d64ede21e9e835e59ecd85329ebefd43faa8211"><code>5d64ede</code></a>
Remove Hash API (<a
href="https://redirect.github.com/astral-sh/uv/issues/21786">#21786</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/9ddc4308551335e9c2ab6571ea0cf529eccf3e76"><code>9ddc430</code></a>
Move shared thread initialization into uv-threads (<a
href="https://redirect.github.com/astral-sh/uv/issues/21746">#21746</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/ef1e0689b4612796d7781ebeae7866028c6c9e82"><code>ef1e068</code></a>
Make Git stamping opt-in for development builds (<a
href="https://redirect.github.com/astral-sh/uv/issues/21750">#21750</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/6ce09770cec30b30cdc66c3595517dce2b112562"><code>6ce0977</code></a>
Avoid panics for non-base index URLs (<a
href="https://redirect.github.com/astral-sh/uv/issues/21784">#21784</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/47f19ec154f325ade7d6045c4e785531c24635f7"><code>47f19ec</code></a>
Return an error for failed VerbatimUrl path conversions (<a
href="https://redirect.github.com/astral-sh/uv/issues/21783">#21783</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/uv/compare/0.12.13...0.12.16">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-19 23:18:59 -04:00
splitsec2andInfiniteAvenger 44f4e13cce refactor: extract the per-source release search out of /api/releases (#1355)
The `/api/releases` route carries an inner `_search_source_releases`
helper that builds the search plan for one source, logs the planned
query type, runs the search and turns
`SourceUnavailableError`/operational errors into an error message
instead of raising. Anything outside the route that wants to search one
source with exactly those semantics has to go through Flask today.

This moves that helper into `shelfmark/core/release_search.py` as
`search_source_releases()` and has the route delegate to it. Behaviour
is unchanged: same plan construction (including the caller's `user_id`,
so per-user default languages still apply), same logging, same
error-to-message handling.

It is the refactor half of #1047 by @InfiniteAvenger, split out on its
own as you asked for other PRs (#1318). Their authorship is preserved on
the commit; I rebased it onto current `main` and added tests.

## Verification

- `tests/core/test_release_search.py`: unknown source → `"Unknown
source: …"`, `SourceUnavailableError` and operational errors →
`"<source>: <error>"`, success path forwards `expand_search` /
`content_type` and returns the source instance, the plan receives
languages / manual query / indexers / `user_id`. (These tests are
type-annotated; happy to strip the annotations if you prefer the suite's
bare style.)
- Full suite, ruff, ruff format, basedpyright, vulture green; the
existing `/api/releases` route tests are unchanged and pass.

Co-authored-by: InfiniteAvenger <calebewest02@gmail.com>
2026-09-19 23:16:53 -04:00
Marcelo Rodrigo b7002a6eca feat: Add TorBox client support and settings integration (#1342)
Add **TorBox** as a torrent download client for Prowlarr releases.

Users can select `TorBox` in the download client settings, configure it
with the new `TORBOX_API_KEY` environment variable, and verify their
credentials with the connection test button.

The integration supports both magnet links and `.torrent` files. It
tracks the torrent lifecycle through TorBox, downloads supported book
and audiobook files from the TorBox CDN, preserves safe nested file
paths, and cleans up remote and local download state.

Important: Shared HTTP download logs omit full download URLs and
URL-bearing exception text to avoid exposing credentials, following best
practices. This applies to all clients that use the shared
`download_url()` path; URLs remain available to the HTTP operations
themselves.

---
There is already related work in progress in #1173, which includes both
torrent and direct-download support for TorBox.

This PR is not intended to replace or compete with that contribution. It
offers the tested torrent client functionality as a smaller, focused
change that can make TorBox available to the community sooner. The
direct-download integration proposed in #1173 remains valuable and could
be reviewed or introduced separately.

Automated tests cover configuration, connection validation, magnet and
torrent-file submission, API errors, status and progress handling, file
retrieval, path traversal protection, cancellation, cleanup, and
sensitive URL redaction.

I also validated the complete flow locally with several magnet links and
`.torrent` downloads. TorBox processed the torrents and Shelfmark
downloaded the resulting files as expected.

AI was used to help with the implementation, with human validation. This
PR and long description? Took me some good minutes at night after work,
but gives me joy to open this PR to share with the community this
improvement.
2026-09-19 23:15:57 -04:00
Paul Rimmer aafce2be1d Added the ability to sort direct search results by Most downloads (#1351)
Now that we know about the AA Downloads stats for search results
(#1336), this PR allows sorting them locally by "Most downloads" without
doing another AA search. Not needing to do an AA search for this is
faster than the other sorts which do new AA searches asking for their
results to be sorted a certain way. Also added the "Settings->Search
Mode->Default Sort Order->Most downloads" option. Universal downloads do
not allow for results sorting so I did not include this feature there.

NOTE: While adding this feature I noticed that shelfmark only looks at
the first page of AA search results. So switching between different sort
orders may result in different sets of results with the same search
term.

Here's a couple of pics showing the new sort option:

<img width="1186" height="759"
alt="search-mode-sort-order-most-downloads"
src="https://github.com/user-attachments/assets/c43fe2e7-84ba-48cd-a850-383f1af0d28f"
/>

<img width="1172" height="828"
alt="direct-search-results-most-downloads"
src="https://github.com/user-attachments/assets/4e240180-6a9d-4ce1-ab86-006b286570dd"
/>

Coded with llama.cpp, opencode and 🤖
2026-09-19 23:12:36 -04:00
dependabot[bot] cdd001bdd9 build(deps): bump python-socketio from 5.16.4 to 5.17.0 in the python-deps group (#1347)
Bumps the python-deps group with 1 update:
[python-socketio](https://github.com/sponsors/miguelgrinberg).

Updates `python-socketio` from 5.16.4 to 5.17.0
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/sponsors/miguelgrinberg/commits">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=python-socketio&package-manager=uv&previous-version=5.16.4&new-version=5.17.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-18 00:14:55 -04:00
CaliBrain e5dd34ae0e fix: unbreak main and follow up on the Blackhole handoff review (#1346)
DownloadHistoryService.record_download and updated the single production
caller, but not the eleven in the test suite, leaving main red with 32
failures. Pass None, which is what the pre-#1336 behaviour recorded.

For the Blackhole handoff (#1345): add_download publishes the torrent
before the cancel check runs, and BlackholeClient.remove() is a no-op,
so
the watcher picks the file up regardless. Reporting a bare "Cancelled"
hid
that from the user. Name the completed handoff in the cancellation
message
instead, drop the _handle_cancelled_download call whose usenet branch
cannot apply to a handoff-only client, and record why the orchestrator
no
longer verifies HandoffResult.path.

Finally, make tests/direct_download a package: test_libgen_extract.py
imports tests.libgen.sample_html across test directories, so without an
__init__.py pytest named its modules by bare basename and a same-named
module elsewhere would collide.
2026-09-17 16:27:53 -04:00
dependabot[bot] 38a429acc8 build(deps): bump the python-deps group across 1 directory with 5 updates (#1344)
Bumps the python-deps group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [tqdm](https://github.com/tqdm/tqdm) | `4.70.0` | `4.70.1` |
| [seleniumbase](https://github.com/seleniumbase/SeleniumBase) |
`4.53.7` | `4.54.5` |
| [basedpyright](https://github.com/detachhead/basedpyright) | `1.40.0`
| `1.40.1` |
| [prek](https://github.com/j178/prek) | `0.5.2` | `0.5.3` |
| [ruff](https://github.com/astral-sh/ruff) | `0.16.6` | `0.16.7` |


Updates `tqdm` from 4.70.0 to 4.70.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tqdm/tqdm/releases">tqdm's
releases</a>.</em></p>
<blockquote>
<h2>tqdm v4.70.1 stable</h2>
<ul>
<li><code>contrib.concurrent</code>: fix no-len iterables (<a
href="https://redirect.github.com/tqdm/tqdm/issues/1830">#1830</a> &lt;-
<a
href="https://redirect.github.com/tqdm/tqdm/issues/1828">#1828</a>)</li>
<li>tests: major overhaul (<a
href="https://redirect.github.com/tqdm/tqdm/issues/1819">#1819</a>)</li>
<li>update AI policy in PR template</li>
<li>misc lint &amp; tidy</li>
<li>CI: bump workflow actions &amp; pre-commit hooks</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/tqdm/tqdm/commit/9cf5a12b1f955468a17f0ba3c59092b23e4258ac"><code>9cf5a12</code></a>
Merge pull request <a
href="https://redirect.github.com/tqdm/tqdm/issues/1830">#1830</a> from
eaubin/master</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/24b9e1e08a097f1c30f2d235b63f9eb25ba47974"><code>24b9e1e</code></a>
misc tidy</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/2a9e4e82ddba07c5dd6d126e23a4745b76ea49c8"><code>2a9e4e8</code></a>
contrib.concurrent: fix no-len iterables</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/8d6ff8de5a9066de77d0a9df3e80a022a3dcf146"><code>8d6ff8d</code></a>
Merge pull request <a
href="https://redirect.github.com/tqdm/tqdm/issues/1819">#1819</a> from
tqdm/devel</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/bed379609c2c8300671c784c8f85a185c8cf6fb3"><code>bed3796</code></a>
tests: major overhaul</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/4d3d3194d5467e0701bc0f4b836a735273ef0868"><code>4d3d319</code></a>
AI policy</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/5dcb022b55553b60ed2131b64aa5bf1e65470df1"><code>5dcb022</code></a>
minor syntax update</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/309effe4244bf45427c5add7f1bcf33f98a7b421"><code>309effe</code></a>
tests: pre-commit coverage</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/604193aa03b5f5c0ad5f3d441951f0fbb515f1e4"><code>604193a</code></a>
tests: slight tidy</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/17f1cac07c89943eb0e6082875b3821205308d4f"><code>17f1cac</code></a>
CI: bump workflow actions</li>
<li>See full diff in <a
href="https://github.com/tqdm/tqdm/compare/v4.70.0...v4.70.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `seleniumbase` from 4.53.7 to 4.54.5
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.54.5 - MCP Server: Patch 14</h2>
<h2>MCP Server: Patch 14</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/753846cca13b6ead76603781b09f725b3236ae79">Update
the MCP Server</a>
--&gt; Going for a top tool score in Glama.</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/fda62a2b83b9de176342f1c21c5f72032ffd4628">Update
the MCP Server ReadMe</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/b6cd1e7c787cbe2c580e624e3576112d0a0de959">Refresh
optional Python dependencies</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>MCP Server: Patch 14 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4501">seleniumbase/SeleniumBase#4501</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.54.4...v4.54.5">https://github.com/seleniumbase/SeleniumBase/compare/v4.54.4...v4.54.5</a></p>
<h2>4.54.4 - MCP Server: Patch 13</h2>
<h2>MCP Server: Patch 13</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/ec41b905a0202e13e9ed0b2f767cd755ea0f891c">Update
CDP Mode (add timeout to a few methods)</a>
--&gt; (One of the ReadMe files should've been committed in the next
commit with a different message)</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/eaef20040fe624f2b51e369e1ea8bf5c34bb4d9c">Update
the MCP Server</a>
--&gt; (The MCP ReadMe was updated in the previous commit with the wrong
commit message)</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>MCP Server: Patch 13 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4500">seleniumbase/SeleniumBase#4500</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.54.3...v4.54.4">https://github.com/seleniumbase/SeleniumBase/compare/v4.54.3...v4.54.4</a></p>
<h2>4.54.3 - MCP Server: Patch 12</h2>
<h2>MCP Server: Patch 12</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/0d04ec82f7b2fa22bd84a67ad07a119c08a62d06">Update
the MCP Server</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/f4a86268b1177044e0a4d03348f5fab76838b96b">Update
the MCP Server ReadMe</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>MCP Server: Patch 12 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4499">seleniumbase/SeleniumBase#4499</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.54.2...v4.54.3">https://github.com/seleniumbase/SeleniumBase/compare/v4.54.2...v4.54.3</a></p>
<h2>4.54.2 - MCP Server: Patch 11</h2>
<h2>MCP Server: Patch 11</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/7ea1bbfdae22951c3914a902c46a8520253eaf05">Update
CDP Mode</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/1aa89a962424c6f3b930449844665a3fa2628d55">Update
the MCP Server</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>MCP Server: Patch 11 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4498">seleniumbase/SeleniumBase#4498</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.54.1...v4.54.2">https://github.com/seleniumbase/SeleniumBase/compare/v4.54.1...v4.54.2</a></p>
<h2>4.54.1 - MCP Server: Patch 10</h2>
<h2>MCP Server: Patch 10</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/3de06e5e22a2f6fe629f47bf5859bc74eebcae48"><code>3de06e5</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4501">#4501</a>
from seleniumbase/mcp-server-patch-14</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/4c0aaa7ca4fae594cc2826d3bdbb08cb91ea8ab3"><code>4c0aaa7</code></a>
Version 4.54.5</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/b6cd1e7c787cbe2c580e624e3576112d0a0de959"><code>b6cd1e7</code></a>
Refresh optional Python dependencies</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/37cffca4f6333d87a9f86a3c1322922ddec85d0b"><code>37cffca</code></a>
Update MCP Server versioning</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/fda62a2b83b9de176342f1c21c5f72032ffd4628"><code>fda62a2</code></a>
Update the MCP Server ReadMe</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/753846cca13b6ead76603781b09f725b3236ae79"><code>753846c</code></a>
Update the MCP Server</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/82e3b51cc6941649dda4eed08d7b3efc0eac734a"><code>82e3b51</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4500">#4500</a>
from seleniumbase/mcp-server-patch-13</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/bc14832f52674fdf9ee574d87bd7b6fed6ae2f8e"><code>bc14832</code></a>
Version 4.54.4</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/8945f2d745770ca06f316e990cd971e76476a1d1"><code>8945f2d</code></a>
Refresh MCP Server versioning</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/eaef20040fe624f2b51e369e1ea8bf5c34bb4d9c"><code>eaef200</code></a>
Update the MCP Server</li>
<li>Additional commits viewable in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.53.7...v4.54.5">compare
view</a></li>
</ul>
</details>
<br />

Updates `basedpyright` from 1.40.0 to 1.40.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/detachhead/basedpyright/releases">basedpyright's
releases</a>.</em></p>
<blockquote>
<h2>v1.40.1 (pyright 1.1.414)</h2>
<h2>What's Changed</h2>
<ul>
<li>Merge 1.1.414 by <a
href="https://github.com/DetachHead"><code>@​DetachHead</code></a> in <a
href="https://redirect.github.com/DetachHead/basedpyright/pull/1883">DetachHead/basedpyright#1883</a></li>
<li>fix multi-line display of indented lines in builtin docstrings by <a
href="https://github.com/tommyming"><code>@​tommyming</code></a> in <a
href="https://redirect.github.com/DetachHead/basedpyright/pull/1882">DetachHead/basedpyright#1882</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/DetachHead/basedpyright/compare/v1.40.0...v1.40.1">https://github.com/DetachHead/basedpyright/compare/v1.40.0...v1.40.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/e7fca69a08abb7cb28fc6bc5e30e24a387066694"><code>e7fca69</code></a>
1.40.1</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/f7b6ad01edff0b53cd523a176c99ba62bf44156e"><code>f7b6ad0</code></a>
add checks when there are multiline indents for doc strings, update (<a
href="https://redirect.github.com/detachhead/basedpyright/issues/1882">#1882</a>)</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/1bc4879cb2a1cffea9ec6009eaa290b301ba4414"><code>1bc4879</code></a>
fix broken docs links</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/845bfe14f2862b3747c8146ebd53bef7644368e8"><code>845bfe1</code></a>
move and baseline basedpyright errors in new build scripts from
upstream</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/a9189ef5e15182e0627dd5a0f28fd2cede4f2bbc"><code>a9189ef</code></a>
prettier fix</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/756bfaa06709cd32c100a3fe3d15156ff3faef6a"><code>756bfaa</code></a>
fix <code>build:extension:dev</code> script</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/a7a2efdab70c919436ff593bdbd8c3b4acaa96b0"><code>a7a2efd</code></a>
syncpack fix</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/5f6749f16bc38608fbf6f0d48b216320676996b8"><code>5f6749f</code></a>
fix tests</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/bb9b24fddf007c547baafbd013270f2a6cb7a2cb"><code>bb9b24f</code></a>
fix zizmor errors in new workflows from upstream</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/b8ccd0e8cd6cc79be5158e7c4ec2691d9c332594"><code>b8ccd0e</code></a>
fix compile errors from merge</li>
<li>Additional commits viewable in <a
href="https://github.com/detachhead/basedpyright/compare/v1.40.0...v1.40.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `prek` from 0.5.2 to 0.5.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/releases">prek's
releases</a>.</em></p>
<blockquote>
<h2>0.5.3</h2>
<h2>Release Notes</h2>
<p>Released on 2026-09-13.</p>
<h3>Enhancements</h3>
<ul>
<li>Add PEP 740 attestations for PyPI releases (<a
href="https://redirect.github.com/j178/prek/pull/2705">#2705</a>)</li>
<li>Add a <code>check-jsonc</code> builtin hook (<a
href="https://redirect.github.com/j178/prek/pull/2682">#2682</a>)</li>
<li>Allow disabling automatic uv installation (<a
href="https://redirect.github.com/j178/prek/pull/2702">#2702</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Fix Julia additional dependency specifiers (<a
href="https://redirect.github.com/j178/prek/pull/2703">#2703</a>)</li>
<li>Update <code>granit-parser</code> to fix YAML flow indentation (<a
href="https://redirect.github.com/j178/prek/pull/2707">#2707</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/clbarnes"><code>@​clbarnes</code></a></li>
<li><a href="https://github.com/j178"><code>@​j178</code></a></li>
<li><a
href="https://github.com/tisonkun"><code>@​tisonkun</code></a></li>
</ul>
<h2>Install prek 0.5.3</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://github.com/j178/prek/releases/download/v0.5.3/prek-installer.sh
| sh
</code></pre>
<h3>Install prebuilt binaries via powershell script</h3>
<pre lang="sh"><code>powershell -ExecutionPolicy Bypass -c &quot;irm
https://github.com/j178/prek/releases/download/v0.5.3/prek-installer.ps1
| iex&quot;
</code></pre>
<h3>Install prebuilt binaries via Homebrew</h3>
<pre lang="sh"><code>brew install prek
</code></pre>
<h2>Download prek 0.5.3</h2>
<table>
<thead>
<tr>
<th>File</th>
<th>Platform</th>
<th>Checksum</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.3/prek-aarch64-apple-darwin.tar.gz">prek-aarch64-apple-darwin.tar.gz</a></td>
<td>Apple Silicon macOS</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.3/prek-aarch64-apple-darwin.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.3/prek-x86_64-apple-darwin.tar.gz">prek-x86_64-apple-darwin.tar.gz</a></td>
<td>Intel macOS</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.3/prek-x86_64-apple-darwin.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.3/prek-aarch64-pc-windows-msvc.zip">prek-aarch64-pc-windows-msvc.zip</a></td>
<td>ARM64 Windows</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.3/prek-aarch64-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.3/prek-x86_64-pc-windows-msvc.zip">prek-x86_64-pc-windows-msvc.zip</a></td>
<td>x64 Windows</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.3/prek-x86_64-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
</tbody>
</table>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/blob/master/CHANGELOG.md">prek's
changelog</a>.</em></p>
<blockquote>
<h2>0.5.3</h2>
<p>Released on 2026-09-13.</p>
<h3>Enhancements</h3>
<ul>
<li>Add PEP 740 attestations for PyPI releases (<a
href="https://redirect.github.com/j178/prek/pull/2705">#2705</a>)</li>
<li>Add a <code>check-jsonc</code> builtin hook (<a
href="https://redirect.github.com/j178/prek/pull/2682">#2682</a>)</li>
<li>Allow disabling automatic uv installation (<a
href="https://redirect.github.com/j178/prek/pull/2702">#2702</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Fix Julia additional dependency specifiers (<a
href="https://redirect.github.com/j178/prek/pull/2703">#2703</a>)</li>
<li>Update <code>granit-parser</code> to fix YAML flow indentation (<a
href="https://redirect.github.com/j178/prek/pull/2707">#2707</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/clbarnes"><code>@​clbarnes</code></a></li>
<li><a href="https://github.com/j178"><code>@​j178</code></a></li>
<li><a
href="https://github.com/tisonkun"><code>@​tisonkun</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/j178/prek/commit/b7eb6027125de7e3b67dd94039a4f69abf3a5fb0"><code>b7eb602</code></a>
Bump version to 0.5.3 (<a
href="https://redirect.github.com/j178/prek/issues/2708">#2708</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/4644f81e1c86eeb8e22a1ba4aaa6dec99b4266c5"><code>4644f81</code></a>
Update granit-parser to fix YAML flow indentation (<a
href="https://redirect.github.com/j178/prek/issues/2707">#2707</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/87f2ea44c258dc6606ca5556a7b3ff0d9a0a601e"><code>87f2ea4</code></a>
Use self-repository syntax in CI workflows</li>
<li><a
href="https://github.com/j178/prek/commit/f39abae3cafd9ff245843b790fdd9b7c2e933b5c"><code>f39abae</code></a>
Add PEP 740 attestations for PyPI releases (<a
href="https://redirect.github.com/j178/prek/issues/2705">#2705</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/f4924dccacd86afab22ae45e634d7f9ca12dd59e"><code>f4924dc</code></a>
Allow disabling automatic uv installation (<a
href="https://redirect.github.com/j178/prek/issues/2702">#2702</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/156c0cec12847513ba764d1d87543ab6d0785c64"><code>156c0ce</code></a>
Fix Julia additional dependency specifiers (<a
href="https://redirect.github.com/j178/prek/issues/2703">#2703</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/c3ede77b83e7d578e31d076863b755b296256a21"><code>c3ede77</code></a>
Add a <code>check-jsonc</code> builtin hook (<a
href="https://redirect.github.com/j178/prek/issues/2682">#2682</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/c0b9749c3ee43f42efd0116624a7922feded3fc2"><code>c0b9749</code></a>
Verify checksums for PyPI/mirror wheel installs (<a
href="https://redirect.github.com/j178/prek/issues/2688">#2688</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/fbe5c66737e679f31018dd438d16774c19c6774d"><code>fbe5c66</code></a>
Update prek hooks (<a
href="https://redirect.github.com/j178/prek/issues/2690">#2690</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/4687a2c57f8ae58cf0e66b65baf49ea72ccb62ba"><code>4687a2c</code></a>
Fix <code>check-hooks-apply</code> for builtin Windows filename
checks</li>
<li>Additional commits viewable in <a
href="https://github.com/j178/prek/compare/v0.5.2...v0.5.3">compare
view</a></li>
</ul>
</details>
<br />

Updates `ruff` from 0.16.6 to 0.16.7
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/releases">ruff's
releases</a>.</em></p>
<blockquote>
<h2>0.16.7</h2>
<h2>Release Notes</h2>
<p>Released on 2026-09-10.</p>
<h3>Preview features</h3>
<ul>
<li>[<code>ruff</code>] Add rule for default values on method receivers
(<code>RUF077</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26700">#26700</a>)</li>
<li>[<code>ruff</code>] Recognize <code>re.prefixmatch</code>
(<code>RUF039</code>, <code>RUF055</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28311">#28311</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Alternate nested quotes inside format spec interpolations (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28259">#28259</a>)</li>
<li>[<code>flake8-implicit-str-concat</code>] Mark fix unsafe when it
creates a docstring (<code>ISC003</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27981">#27981</a>)</li>
<li>[<code>flake8-tidy-imports</code>] Skip fixes for multi-member
imports (<code>TID254</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26584">#26584</a>)</li>
<li>[<code>pylint</code>] Gate <code>ImportCycleError</code> on Python
3.15 (<code>PLW0133</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28310">#28310</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>Correct <code>D211</code> and <code>D203</code> rule conflict
diagnostic (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28444">#28444</a>)</li>
<li>Recognize <code>slice</code> and <code>frozendict</code> generics
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/28477">#28477</a>)</li>
<li>Stop defining <code>__cached__</code> for Python 3.15 (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28476">#28476</a>)</li>
<li>[<code>pyupgrade</code>] Stop recommending removed
<code>typing.no_type_check_decorator</code> (<code>UP035</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28475">#28475</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Reuse parser name lookups when interning (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28399">#28399</a>)</li>
<li>Speed up inherited configuration resolution (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28299">#28299</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Fix <code>line-length</code> path in <code>--config</code> example
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/28392">#28392</a>)</li>
<li>Remove the &quot;Who’s Using Ruff?&quot; list (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28455">#28455</a>)</li>
</ul>
<h3>Other changes</h3>
<ul>
<li>Embed archive checksums in the shell installer (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28281">#28281</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/The-Compiler"><code>@​The-Compiler</code></a></li>
<li><a
href="https://github.com/mdiniz97"><code>@​mdiniz97</code></a></li>
<li><a href="https://github.com/zsol"><code>@​zsol</code></a></li>
<li><a
href="https://github.com/gorewilliams"><code>@​gorewilliams</code></a></li>
<li><a
href="https://github.com/RafaelJohn9"><code>@​RafaelJohn9</code></a></li>
<li><a href="https://github.com/qatcod"><code>@​qatcod</code></a></li>
<li><a href="https://github.com/zanieb"><code>@​zanieb</code></a></li>
<li><a
href="https://github.com/MichaReiser"><code>@​MichaReiser</code></a></li>
<li><a href="https://github.com/ntBre"><code>@​ntBre</code></a></li>
<li><a
href="https://github.com/charliermarsh"><code>@​charliermarsh</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's
changelog</a>.</em></p>
<blockquote>
<h2>0.16.7</h2>
<p>Released on 2026-09-10.</p>
<h3>Preview features</h3>
<ul>
<li>[<code>ruff</code>] Add rule for default values on method receivers
(<code>RUF077</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26700">#26700</a>)</li>
<li>[<code>ruff</code>] Recognize <code>re.prefixmatch</code>
(<code>RUF039</code>, <code>RUF055</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28311">#28311</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Alternate nested quotes inside format spec interpolations (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28259">#28259</a>)</li>
<li>[<code>flake8-implicit-str-concat</code>] Mark fix unsafe when it
creates a docstring (<code>ISC003</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27981">#27981</a>)</li>
<li>[<code>flake8-tidy-imports</code>] Skip fixes for multi-member
imports (<code>TID254</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26584">#26584</a>)</li>
<li>[<code>pylint</code>] Gate <code>ImportCycleError</code> on Python
3.15 (<code>PLW0133</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28310">#28310</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>Correct <code>D211</code> and <code>D203</code> rule conflict
diagnostic (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28444">#28444</a>)</li>
<li>Recognize <code>slice</code> and <code>frozendict</code> generics
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/28477">#28477</a>)</li>
<li>Stop defining <code>__cached__</code> for Python 3.15 (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28476">#28476</a>)</li>
<li>[<code>pyupgrade</code>] Stop recommending removed
<code>typing.no_type_check_decorator</code> (<code>UP035</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28475">#28475</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Reuse parser name lookups when interning (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28399">#28399</a>)</li>
<li>Speed up inherited configuration resolution (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28299">#28299</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Fix <code>line-length</code> path in <code>--config</code> example
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/28392">#28392</a>)</li>
<li>Remove the &quot;Who’s Using Ruff?&quot; list (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28455">#28455</a>)</li>
</ul>
<h3>Other changes</h3>
<ul>
<li>Embed archive checksums in the shell installer (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28281">#28281</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/The-Compiler"><code>@​The-Compiler</code></a></li>
<li><a
href="https://github.com/mdiniz97"><code>@​mdiniz97</code></a></li>
<li><a href="https://github.com/zsol"><code>@​zsol</code></a></li>
<li><a
href="https://github.com/gorewilliams"><code>@​gorewilliams</code></a></li>
<li><a
href="https://github.com/RafaelJohn9"><code>@​RafaelJohn9</code></a></li>
<li><a href="https://github.com/qatcod"><code>@​qatcod</code></a></li>
<li><a href="https://github.com/zanieb"><code>@​zanieb</code></a></li>
<li><a
href="https://github.com/MichaReiser"><code>@​MichaReiser</code></a></li>
<li><a href="https://github.com/ntBre"><code>@​ntBre</code></a></li>
<li><a
href="https://github.com/charliermarsh"><code>@​charliermarsh</code></a></li>
<li><a
href="https://github.com/nightt5879"><code>@​nightt5879</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ruff/commit/b5dba861cc38e3f7fb4524c9ceba3e01a474ea13"><code>b5dba86</code></a>
Bump version to 0.16.7 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28496">#28496</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/5992d0504697d86565d8fc3a4d8245a5f4047d24"><code>5992d05</code></a>
Install rustfmt before linting releases (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28495">#28495</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/1713a1f4325494d883a080d590a25a1946f399e8"><code>1713a1f</code></a>
ensure prepare release changes pass prek (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28488">#28488</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/18cdbb4f3d14058794420e758864795f55336334"><code>18cdbb4</code></a>
use scoped token for release workflow (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28484">#28484</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/c3813a501faf887fd01948c98bb8d58ad26488bd"><code>c3813a5</code></a>
add a workflow for preparing releases (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28486">#28486</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/00948c00a671b81f5358af9f038436bcbb993b38"><code>00948c0</code></a>
Remove the &quot;Who’s Using Ruff?&quot; list (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28455">#28455</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/86a2eba7b48e3c10386f7ab8a5425c7275d2b427"><code>86a2eba</code></a>
[<code>pyupgrade</code>] Stop recommending removed
<code>typing.no_type_check_decorator</code> (`UP...</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/609e184aa35f0034b7ef63e3e04327081c484af6"><code>609e184</code></a>
Stop defining <code>__cached__</code> for Python 3.15 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28476">#28476</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/859ff2f01670c43ffff1ea597c8a2e375ada0fbe"><code>859ff2f</code></a>
[ty] Track symlinked directory status in listings (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28482">#28482</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/77f653825800ddaf3bc221ae6db49a500e1002d5"><code>77f6538</code></a>
Use paid GitHub-hosted runners for Linux (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28478">#28478</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ruff/compare/0.16.6...0.16.7">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-17 15:53:10 -04:00
Vinicius Gabriel c53545d9fe Add configurable word separator for naming templates (#1333)
Closes #1230

## What
Adds a "Word Separator" setting (Space / Dot / Underscore / Hyphen /
Custom) that replaces internal whitespace in each naming-template
placeholder's rendered value — e.g. `{Author}` renders
"Arthur.Conan.Doyle" instead of "Arthur Conan Doyle" when Dot is
selected.

This follows option 2 from the issue rather than inventing new
dotted-keyword template syntax (`{Author.}`), since it's a smaller
surface: one setting applies uniformly across all four templates
(books/audiobooks × rename/organize) instead of needing a parallel token
for every existing one.

## How it works
- Literal characters typed into the template itself (e.g. the `.` in
`{Author}.-.{Title}`) are never touched — only whitespace *inside* a
placeholder's resolved value is affected.
- Default is "Space", which is a no-op: existing templates produce
byte-identical output after this change (verified via the existing test
suite, unmodified, still passing).

## Where
- `shelfmark/core/naming.py` — `word_separator` param on
`parse_naming_template` / `build_library_path`.
- `shelfmark/download/postprocess/policy.py` — `get_word_separator()`,
mirroring the existing `get_file_organization()` accessor.
- `shelfmark/download/postprocess/transfer.py` — wires the resolved
separator through the four existing template-rendering call sites.
- `shelfmark/config/settings.py` — new `Word Separator` / `Custom Word
Separator` fields next to the existing naming-template fields.
- `src/frontend/.../namingTemplatePreview.ts` +
`NamingTemplateField.tsx` — the settings UI has its own TS mirror of the
Python renderer for the live preview; updated it in lockstep so the
preview doesn't lie about what the separator will actually do.
- Tests added on both sides (pytest + vitest).

## Testing
- `uv run pytest tests/core/test_naming.py
tests/core/test_destination_file_organization.py` — all pass, including
new cases.
- `uv run pytest` (full suite) — same pre-existing failures as on `main`
before this change (browser/network-dependent bypass & e2e tests
unrelated to this diff), everything else green.
- `uv run ruff check` / `ruff format --check` / `basedpyright` — clean.
- `npm run lint` / `format:check` / `typecheck` / `test:unit` (196
tests) — clean.
2026-09-17 15:52:21 -04:00
Atirna 8f608f2e64 fix(download): complete consumed Blackhole handoffs (#1345)
A Blackhole watcher can consume the torrent before Shelfmark checks it,
leaving the task in error even though the handoff succeeded. Complete
the handoff when `add_download` successfully publishes the file, and
stop requiring a `HandoffResult` path to remain present.

Follow-up to #1312.

## Verification

- A watcher that immediately reads and removes the torrent receives the
exact bytes. The task changes from ERROR before this fix to COMPLETE
afterward, without running book postprocessing.
- The consumed-file regression fails on current main and passes here.
Resident files, write failures, cancellation, magnet rejection and
normal downloads remain covered: 81 focused tests pass.
- Ruff lint and formatting pass for the changed files.
2026-09-17 15:51:20 -04:00
David Young 2bb84a17a2 Extract archives when zip/rar are enabled as supported formats (#1343)
The default audiobook formats include `zip` and `rar`.
`scan_directory_tree` checks the supported-format list before checking
for archives, so a downloaded archive lands in `book_files` and is
imported as-is. The extraction branch in `collect_directory_files` is
never reached.

This keeps archives out of `book_files`, so they always take the archive
path: extracted when extraction is allowed, imported as-is when it isn't
(unchanged).

Tests added in `tests/download/test_postprocess_scan_archives.py`; three
of the four fail without the change.
2026-09-17 15:50:52 -04:00
Paul Rimmer 2ed2e9a5d4 Feature: Add Download counts to search result displays and Download sidebar (#1336)
Having the download counts from AA as an input on choosing which of the
many search results to pick for downloading is useful. This PR makes the
downloads numbers available on all the search result pages and also on
the Download sidebar after the user presses a download button for a
Direct Download.

I have also included a SKILL.md and associated download_books.py that
can be used just as a reference or with an LLM harness for automation.
The Downloads info is used by the script to pick which search result to
download out of the many available.

Since a picture is worth a thousand words:

<img width="1225" height="812" alt="search-results-with-downloads"
src="https://github.com/user-attachments/assets/e108ebe2-4cad-45e2-bb6a-d4f49b502de9"
/>

<img width="443" height="267" alt="download-sidebar-with-downloads"
src="https://github.com/user-attachments/assets/9ce9e6c4-dcf5-4f7c-ab22-71850398b534"
/>

Coded with llama.cpp and 🤖
2026-09-17 15:49:57 -04:00
Alex Guerrieri c6b70a6844 fix(sources): send a Referer when fetching libgen ads.php pages (#1340)
## What

libgen.li's `ads.php?md5=` now returns an **empty `200`** to any request
without a `Referer` — an anti-hotlinking check the mirrors added
recently. Both libgen paths fetch it without one, so the page comes back
blank and the download silently fails while **search keeps working**
(which is exactly why it looks like rate-limiting or mirror drift rather
than a bug).

Same one-line cause, two call sites: the Libgen search source
(`libgen/scraper.py:fetch_page`) and the AA-md5 → libgen fallback
(`direct_download/annas_archive.py:_extract_libgen_download_url`). Fix:
send a same-origin `Referer: <scheme>://<host>/` on the `ads.php` fetch
in both.

## Worth a look in review

- **The referer goes on the *resolution* fetch, not the download.**
`download_url(..., referer=...)` was already correct — the blank page
happens one step earlier, at the `ads.php` GET.
- Reproduced against live mirrors: `ads.php` returns `Content-Length: 0`
bare, the full page with a `Referer`, and resolvable files download
valid bytes again.

Regression tests in `tests/libgen/` and `tests/direct_download/` assert
the header on both paths. Lint/format/typecheck clean.

Follow-up to #1326.
2026-09-17 15:47:45 -04:00
CaliBrain 35b89b0d78 fix(sources): restore Direct Download search errors and language matches (#1339)
Fixes two regressions from the provider-driven refactor (#1337). First,
the composite search caught RuntimeError, TypeError, ValueError and
request errors from each provider and returned an empty list, so a
failed search looked like one with no hits. It now raises the first
provider failure when no provider returned releases. Second, the shared
parser re-matched every row's language locally, dropping rows Anna's
Archive had already matched with &lang= (free-text cells like 'English,
French' or 'unknown'). parse_search_items gains a filter_languages
option, which AA turns off, so AA's own language-from-path filter is
again the only local one.
2026-09-14 01:30:05 -04:00
Tom a5cd9f0bfb refactor: make direct download provider-driven (#1337)
This is the refactor for the download handler
2026-09-14 01:16:53 -04:00
Alex Guerrieri af21d1da1f feat(sources): add Libgen as a direct catalogue search source (#1326)
## What

Adds **Libgen as a search source**. Today Libgen is only a download
mirror (reached by an Anna's Archive md5), so anything in Libgen but not
in AA's search index is invisible — and that's where most of the CBZ/CBR
comics and manga live. A Libgen search for *One Piece*, for instance,
turns up ~99 volumes that AA search never shows.

It's a self-contained `release_sources/libgen/` package (source +
handler + settings) plus one line to register it. **No changes to
`direct_download.py`** — it reuses the existing `ads.php → get.php`
resolution and the mirrors already configured in `LIBGEN_MIRROR_URLS`.
Plain HTTP, no bypasser needed (libgen.li isn't behind DDoS-Guard).
Opt-in via a settings toggle.

## Worth a look in review

- **`source_id` is `libgen:<md5>`, not the bare md5.** The download
queue keys on `task_id` (= `source_id`), and `direct_download` already
uses the bare md5. Since AA indexes a lot of Libgen, the same md5 shows
up from both sources — a bare id would collide in the queue. The handler
strips the prefix before downloading.
- **Reachable like the other non-default sources** (Prowlarr,
AudiobookBay, …): it appears in the per-book release search, not the
free-text box (that stays wired to `direct_download`).

Tests in `tests/libgen/` cover parsing (both row layouts), the source,
the handler, and `get_record`. Lint/format/typecheck clean.
2026-09-14 01:08:08 -04:00
CaliBrain 1b17fe179a fix(irc): rank a surname-only result as partial, not wrong (#1332) (#1334)
"David Petrie" as "D. Petrie", then ranked the answer by the full name
to
recover the precision the surname gave up. The two halves disagreed.

author_affinity needs two agreeing tokens before it calls a name the
same
person, so "Petrie" - the name on the filenames a surname search exists
to
reach - matched one and came back AUTHOR_MISMATCH. It therefore sorted
below
"Unknown" and level with "Gordon Petrie", a different author who merely
shares
the surname. The widened query pulled those rows in and the ranker
buried them.

Falling short of agreement is now separated from disagreeing with it. A
name
whose every token fits the one asked for is an abbreviation of it and
ranks
AUTHOR_PARTIAL, between agreement and "no author reported"; a name
carrying a
token that fits nothing still ranks AUTHOR_MISMATCH. Nothing that agreed
before
changes tier - "Homer"/"Homer Simpson" is still a match, since the extra
token
must not demote a mononym that already met its one-token requirement -
so
Prowlarr's #1293 ordering is unchanged except that a tracker listing a
bare
surname stops being read as the wrong author.

Measured on the issue's own case, wanted "David Petrie":

  before: D Petrie, Unknown, Petrie, Gordon Petrie
  after:  D Petrie, Petrie, Unknown, Gordon Petrie

Second fix, same release: a book with no title posted the surname on its
own.
_build_query fell back to book.search_title or book.title, which is
empty on
exactly the path where the plan has no title variants, so the line
reaching the
channel was "@search Petrie" - not a search for anything, and the kind
of bare
over-broad post is_available refuses unaddressed queries to avoid. It
now
returns "" and the existing "No search query could be built" guard takes
it.

Tested with make python-lint, python-format, python-dead-code,
python-typecheck
and python-test.
2026-09-11 22:26:28 -04:00
Zoltán Szabó 35037b35fd fix(irc): search by surname, and rank the answer by author (#1331) (#1332)
Fixes #1331.

A search bot ANDs every term against a filename, so the given name is
the term
that empties the result set. Measured against irchighway's #ebooks:
"Revelations
David Petrie" is answered "no results", "Revelations Petrie" returns 9
matches,
6 of which parse, all filed as "D Petrie".

The query now carries the title and the surname, read off the search
variant so
the ISBN fallback and a manual query - which set author="" on purpose -
keep
their current shape.

Title-only, the shape #1295 settled on for Prowlarr, does not transfer:
the bot
caps an answer at 1000 matches, and a bare "Revelations" hits that cap
with 923
parsed rows across 500 authors, so the cap itself can drop the wanted
book. The
surname is the token the two spellings share and it keeps the answer
small.

The full author then orders what comes back, reusing author_affinity
from #1295,
since a surname also matches a different author who shares it. It sits
under
server availability the way indexer priority does in #1295: a download
addresses
one named bot and waits 120s for it, so a match from a bot that has left
the
channel must not outrank a mismatch that can answer. Ranking runs on the
way out
rather than before the cache, because one query identity is shared by
every book
that produced that query.

Two things found while testing:

- The parser writes the literal "Unknown" when a filename has no " - "
separator
  (parser.py:168). Ranked literally that sorts as a wrong author, so
author_affinity's middle tier was unreachable here; it is now read as
absent.
  5 of those 923 rows are affected.
- author_affinity moves to shelfmark/core/author_match.py, unchanged, so
IRC
does not import from the Prowlarr package. Prowlarr behaviour is
untouched and
  its tests pass as they are.

The three IRC assertions in the #1252 regression file move to the
surname form.
The invariant they pin - one contributor's name reaches the query, never
the
whole credit list - is unchanged.

Tested with make python-lint, python-format, python-dead-code,
python-typecheck
and python-test, and end to end against irchighway with the patched
source: it
posts "Revelations Petrie" and returns 6 releases.
2026-09-11 22:14:37 -04:00
dependabot[bot] 8c902d7f7a build(deps-dev): bump vitest from 4.1.11 to 5.0.0 in /src/frontend (#1330)
Bumps
[vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)
from 4.1.11 to 5.0.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitest-dev/vitest/releases">vitest's
releases</a>.</em></p>
<blockquote>
<h2>v5.0.0</h2>
<p>Vitest 5 is officially out! This release focuses on performance and
brings a lot of new features while fixing long-standing bugs. See our <a
href="https://vitest.dev/blog/vitest-5.html">blog post</a> for the
official announcement.</p>
<h3>   🚨 Breaking Changes</h3>
<ul>
<li>Replace <code>loupe.inspect</code> with pretty-format  -  by <a
href="https://github.com/hi-ogawa"><code>@​hi-ogawa</code></a>,
<strong>Claude Opus 5 (1M context)</strong> and <strong>OpenAI
Codex</strong> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/9609">vitest-dev/vitest#9609</a>
<a href="https://github.com/vitest-dev/vitest/commit/3f802da4b"><!-- raw
HTML omitted -->(3f802)<!-- raw HTML omitted --></a></li>
<li>Remove quotes from string values in <code>test.for/each</code> title
<code>$</code> variable (take 2)  -  by <a
href="https://github.com/hi-ogawa"><code>@​hi-ogawa</code></a> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10170">vitest-dev/vitest#10170</a>
<a href="https://github.com/vitest-dev/vitest/commit/04d37e9d7"><!-- raw
HTML omitted -->(04d37)<!-- raw HTML omitted --></a></li>
<li>Default <code>attachmentsDir</code> from
<code>.vitest-attachements/</code> to <code>.vitest/attachments/</code>
 -  by <a
href="https://github.com/MdSadiqMd"><code>@​MdSadiqMd</code></a> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10186">vitest-dev/vitest#10186</a>
<a href="https://github.com/vitest-dev/vitest/commit/1ba7338c3"><!-- raw
HTML omitted -->(1ba73)<!-- raw HTML omitted --></a></li>
<li>Remove <code>sequential</code> test/suite options in favor of
<code>concurrent</code>  -  by <a
href="https://github.com/hi-ogawa"><code>@​hi-ogawa</code></a> and
<strong>OpenAI Codex</strong> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10198">vitest-dev/vitest#10198</a>
<a href="https://github.com/vitest-dev/vitest/commit/9229f2edc"><!-- raw
HTML omitted -->(9229f)<!-- raw HTML omitted --></a></li>
<li>Represent locator as an object instead of a string  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10212">vitest-dev/vitest#10212</a>
<a href="https://github.com/vitest-dev/vitest/commit/80f07edf6"><!-- raw
HTML omitted -->(80f07)<!-- raw HTML omitted --></a></li>
<li>Inline <code>expect</code> package  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10221">vitest-dev/vitest#10221</a>
<a href="https://github.com/vitest-dev/vitest/commit/ad16223e7"><!-- raw
HTML omitted -->(ad162)<!-- raw HTML omitted --></a></li>
<li>Remove deprecated entry points  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10222">vitest-dev/vitest#10222</a>
<a href="https://github.com/vitest-dev/vitest/commit/994c6ddb9"><!-- raw
HTML omitted -->(994c6)<!-- raw HTML omitted --></a></li>
<li>Require Node.js 22 and Vite 6.4  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10178">vitest-dev/vitest#10178</a>
<a href="https://github.com/vitest-dev/vitest/commit/3876283e8"><!-- raw
HTML omitted -->(38762)<!-- raw HTML omitted --></a></li>
<li>Fail <code>expect.poll</code> when function didn't resolve in time
 -  by <a href="https://github.com/hi-ogawa"><code>@​hi-ogawa</code></a>
and <strong>OpenAI Codex</strong> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10233">vitest-dev/vitest#10233</a>
<a href="https://github.com/vitest-dev/vitest/commit/4df048c11"><!-- raw
HTML omitted -->(4df04)<!-- raw HTML omitted --></a></li>
<li>Throw an error if hoistable methods are outside the top level scope
 -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10460">vitest-dev/vitest#10460</a>
<a href="https://github.com/vitest-dev/vitest/commit/d0b4fddcb"><!-- raw
HTML omitted -->(d0b4f)<!-- raw HTML omitted --></a></li>
<li><code>toHaveTextContent</code> is strict, add
<code>toMatchTextContent</code> as alternative  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10473">vitest-dev/vitest#10473</a>
<a href="https://github.com/vitest-dev/vitest/commit/18f303079"><!-- raw
HTML omitted -->(18f30)<!-- raw HTML omitted --></a></li>
<li>Don't lookup config file from ancestor directories  -  by <a
href="https://github.com/hi-ogawa"><code>@​hi-ogawa</code></a>,
<strong>OpenAI Codex</strong> and <strong>Hiroshi Ogawa</strong> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10428">vitest-dev/vitest#10428</a>
<a href="https://github.com/vitest-dev/vitest/commit/945d9090e"><!-- raw
HTML omitted -->(945d9)<!-- raw HTML omitted --></a></li>
<li>Inline <code>@vitest/runner</code> package, do not publish it
anymore  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10511">vitest-dev/vitest#10511</a>
<a href="https://github.com/vitest-dev/vitest/commit/6d6e46b1e"><!-- raw
HTML omitted -->(6d6e4)<!-- raw HTML omitted --></a></li>
<li>Allow mutating happy-dom/jsdom window object  -  by <a
href="https://github.com/hi-ogawa"><code>@​hi-ogawa</code></a>,
<strong>Hiroshi Ogawa</strong> and <strong>OpenAI Codex</strong> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10373">vitest-dev/vitest#10373</a>
<a href="https://github.com/vitest-dev/vitest/commit/206e8cff8"><!-- raw
HTML omitted -->(206e8)<!-- raw HTML omitted --></a></li>
<li>Expose <code>concurrencyId</code>/<code>workerId</code> on
TestModule's diagnostics, make id 1-based  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10516">vitest-dev/vitest#10516</a>
<a href="https://github.com/vitest-dev/vitest/commit/bdd985433"><!-- raw
HTML omitted -->(bdd98)<!-- raw HTML omitted --></a></li>
<li>Add <code>screenshotDirectory</code> config to
<code>browser.expect.toMatchScreenshot</code>  -  by <a
href="https://github.com/macarie"><code>@​macarie</code></a> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10592">vitest-dev/vitest#10592</a>
<a href="https://github.com/vitest-dev/vitest/commit/a60ded0fb"><!-- raw
HTML omitted -->(a60de)<!-- raw HTML omitted --></a></li>
<li>Update <code>@sinonjs/fake-timers</code> and support mocking
<code>Temporal</code>  -  by <a
href="https://github.com/hi-ogawa"><code>@​hi-ogawa</code></a>,
<strong>Hiroshi Ogawa</strong> and <strong>OpenCode
(gpt-5.6-sol)</strong> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10654">vitest-dev/vitest#10654</a>
<a href="https://github.com/vitest-dev/vitest/commit/f8b1532fe"><!-- raw
HTML omitted -->(f8b15)<!-- raw HTML omitted --></a></li>
<li>Remove webdriverio package  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10675">vitest-dev/vitest#10675</a>
<a href="https://github.com/vitest-dev/vitest/commit/5fed68f72"><!-- raw
HTML omitted -->(5fed6)<!-- raw HTML omitted --></a></li>
<li>Clear mocks by default before each test  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10613">vitest-dev/vitest#10613</a>
<a href="https://github.com/vitest-dev/vitest/commit/0f6463bf2"><!-- raw
HTML omitted -->(0f646)<!-- raw HTML omitted --></a></li>
<li>Don't emit localStorage warnings on Node 26, fail gracefully when
worker fails to start  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10293">vitest-dev/vitest#10293</a>
<a href="https://github.com/vitest-dev/vitest/commit/334edef92"><!-- raw
HTML omitted -->(334ed)<!-- raw HTML omitted --></a></li>
<li>Separate config resolution from the server creation  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10554">vitest-dev/vitest#10554</a>
<a href="https://github.com/vitest-dev/vitest/commit/1c0ec3444"><!-- raw
HTML omitted -->(1c0ec)<!-- raw HTML omitted --></a></li>
<li>Inline projects extend the root config by default  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10750">vitest-dev/vitest#10750</a>
<a href="https://github.com/vitest-dev/vitest/commit/fec001ad3"><!-- raw
HTML omitted -->(fec00)<!-- raw HTML omitted --></a></li>
<li>Enable mocking Temporal without fake timers  -  by <a
href="https://github.com/fabon-f"><code>@​fabon-f</code></a>,
<strong>Hiroshi Ogawa</strong> and <strong>OpenCode
(gpt-5.6-sol)</strong> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10757">vitest-dev/vitest#10757</a>
<a href="https://github.com/vitest-dev/vitest/commit/ac2d46b42"><!-- raw
HTML omitted -->(ac2d4)<!-- raw HTML omitted --></a></li>
<li>Support nested projects  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10846">vitest-dev/vitest#10846</a>
<a href="https://github.com/vitest-dev/vitest/commit/ec367cf2a"><!-- raw
HTML omitted -->(ec367)<!-- raw HTML omitted --></a></li>
<li>Use <code>&gt;</code> as separator in <code>-t</code>, calculate
<code>only</code> once  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10686">vitest-dev/vitest#10686</a>
<a href="https://github.com/vitest-dev/vitest/commit/a0b20bc86"><!-- raw
HTML omitted -->(a0b20)<!-- raw HTML omitted --></a></li>
<li>Fail the test when an asynchronous assertion is not awaited  -  by
<a href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a>
in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10868">vitest-dev/vitest#10868</a>
<a href="https://github.com/vitest-dev/vitest/commit/86d4a9da9"><!-- raw
HTML omitted -->(86d4a)<!-- raw HTML omitted --></a></li>
<li>Share the Vite server between inline projects  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10848">vitest-dev/vitest#10848</a>
<a href="https://github.com/vitest-dev/vitest/commit/d87c96ee4"><!-- raw
HTML omitted -->(d87c9)<!-- raw HTML omitted --></a></li>
<li>Parse files statically in vitest list by default  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/11088">vitest-dev/vitest#11088</a>
<a href="https://github.com/vitest-dev/vitest/commit/51e949416"><!-- raw
HTML omitted -->(51e94)<!-- raw HTML omitted --></a></li>
<li><strong>benchmark</strong>:
<ul>
<li>Rewrite the public API  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10113">vitest-dev/vitest#10113</a>
<a href="https://github.com/vitest-dev/vitest/commit/19f6e8947"><!-- raw
HTML omitted -->(19f6e)<!-- raw HTML omitted --></a></li>
</ul>
</li>
<li><strong>browser</strong>:
<ul>
<li>Iframe scale  -  by <a
href="https://github.com/macarie"><code>@​macarie</code></a> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/9745">vitest-dev/vitest#9745</a>
<a href="https://github.com/vitest-dev/vitest/commit/b639852cc"><!-- raw
HTML omitted -->(b6398)<!-- raw HTML omitted --></a></li>
<li>Enable <code>locators.exact</code> by default  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10430">vitest-dev/vitest#10430</a>
<a href="https://github.com/vitest-dev/vitest/commit/e203202f9"><!-- raw
HTML omitted -->(e2032)<!-- raw HTML omitted --></a></li>
<li>Require <code>sessionId</code> for orchestrator html request  -  by
<a href="https://github.com/hi-ogawa"><code>@​hi-ogawa</code></a>,
<strong>Hiroshi Ogawa</strong> and <strong>OpenAI Codex</strong> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10522">vitest-dev/vitest#10522</a>
<a href="https://github.com/vitest-dev/vitest/commit/79b7d8fcc"><!-- raw
HTML omitted -->(79b7d)<!-- raw HTML omitted --></a></li>
<li>Save failure screenshots in <code>attachmentsDir</code>  -  by <a
href="https://github.com/macarie"><code>@​macarie</code></a> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10917">vitest-dev/vitest#10917</a>
<a href="https://github.com/vitest-dev/vitest/commit/3b5bbd8b4"><!-- raw
HTML omitted -->(3b5bb)<!-- raw HTML omitted --></a></li>
</ul>
</li>
<li><strong>coverage</strong>:
<ul>
<li><code>include/exclude</code> globs too eager  -  by <a
href="https://github.com/AriPerkkio"><code>@​AriPerkkio</code></a> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/9818">vitest-dev/vitest#9818</a>
<a href="https://github.com/vitest-dev/vitest/commit/edacb0fd4"><!-- raw
HTML omitted -->(edacb)<!-- raw HTML omitted --></a></li>
<li>Allow <code>thresholds.perFile</code> to accept an object  -  by <a
href="https://github.com/vladlenskiy"><code>@​vladlenskiy</code></a> and
<a href="https://github.com/AriPerkkio"><code>@​AriPerkkio</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10190">vitest-dev/vitest#10190</a>
<a href="https://github.com/vitest-dev/vitest/commit/13b78d98b"><!-- raw
HTML omitted -->(13b78)<!-- raw HTML omitted --></a></li>
</ul>
</li>
<li><strong>expect</strong>:
<ul>
<li>Fix <code>toThrow(&quot;&quot;)</code> behavior by reverting <a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/6710">#6710</a>
 -  by <a href="https://github.com/hi-ogawa"><code>@​hi-ogawa</code></a>
in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/9643">vitest-dev/vitest#9643</a>
and <a
href="https://redirect.github.com/vitest-dev/vitest/issues/6710">vitest-dev/vitest#6710</a>
<a href="https://github.com/vitest-dev/vitest/commit/6c3e4bdbf"><!-- raw
HTML omitted -->(6c3e4)<!-- raw HTML omitted --></a></li>
</ul>
</li>
<li><strong>mocker</strong>:
<ul>
<li>Deserialize automock as automock  -  by <a
href="https://github.com/nami8824"><code>@​nami8824</code></a> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10192">vitest-dev/vitest#10192</a>
<a href="https://github.com/vitest-dev/vitest/commit/2f892712d"><!-- raw
HTML omitted -->(2f892)<!-- raw HTML omitted --></a></li>
</ul>
</li>
<li><strong>reporters</strong>:
<ul>
<li><code>blob</code> reporter and <code>--merge-reports</code> default
to <code>.vitest/blob/</code>  -  by <a
href="https://github.com/AriPerkkio"><code>@​AriPerkkio</code></a> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10232">vitest-dev/vitest#10232</a>
<a href="https://github.com/vitest-dev/vitest/commit/d22b029ae"><!-- raw
HTML omitted -->(d22b0)<!-- raw HTML omitted --></a></li>
<li>Write json and junit reporter output files to <code>.vitest</code>
by default  -  by <a
href="https://github.com/hi-ogawa"><code>@​hi-ogawa</code></a>,
<strong>Hiroshi Ogawa</strong>, <strong>OpenCode (gpt-5.6-sol)</strong>
and <a
href="https://github.com/AriPerkkio"><code>@​AriPerkkio</code></a> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10621">vitest-dev/vitest#10621</a>
<a href="https://github.com/vitest-dev/vitest/commit/58577290a"><!-- raw
HTML omitted -->(58577)<!-- raw HTML omitted --></a></li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/vitest-dev/vitest/commit/f441c6fab25e579c5b7dd3dd50538416f415fbae"><code>f441c6f</code></a>
chore: release v5.0.0 (<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/11130">#11130</a>)</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/d46a7472266d5bb43595ea51dcdd64ab0f560f12"><code>d46a747</code></a>
fix: treat test.describe as a suite during static collection (<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/11128">#11128</a>)</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/584cf308437069385b0fb905fa3ff7d7b7f65a90"><code>584cf30</code></a>
fix: add a warning if inline project has duplicate plugins due to
unexpected ...</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/f08ce4b7144542af128dcb884150c42074223653"><code>f08ce4b</code></a>
fix: apply queued mocks from doMock() in queue order (fixes <a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/10706">#10706</a>)
(<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/11127">#11127</a>)</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/897f51fd2493046c52ec9539b7d02fe3763bd63e"><code>897f51f</code></a>
chore: release v5.0.0-rc.4 (<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/11107">#11107</a>)</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/1339b0655dc4679aeb8f905bacee83b6f76f6b23"><code>1339b06</code></a>
chore(deps): update all non-major dependencies (<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/11104">#11104</a>)</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/51e9494166d8d0108a621fce80c41b6dba760bae"><code>51e9494</code></a>
feat!: parse files statically in vitest list by default (<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/11088">#11088</a>)</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/2122ffdfb42d86c9a2f1238100d6a031312cae32"><code>2122ffd</code></a>
fix: propagate --maxWorkers to projects (<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/11102">#11102</a>)</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/dc10f5f8fb69b026afcf56f8a7a9dd36bd964d73"><code>dc10f5f</code></a>
fix(browser): report the action error when a task times out (<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/11101">#11101</a>)</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/d4fe1986fe334d22940039e815ccdbcc907baa6a"><code>d4fe198</code></a>
feat: promote clearCache out of experimental (<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/11086">#11086</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/vitest-dev/vitest/commits/v5.0.0/packages/vitest">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=vitest&package-manager=npm_and_yarn&previous-version=4.1.11&new-version=5.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-11 02:07:57 -04:00
dependabot[bot] 68de5de241 build(deps-dev): bump the npm-deps group in /src/frontend with 4 updates (#1329)
Bumps the npm-deps group in /src/frontend with 4 updates:
[@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node),
[@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom),
[oxfmt](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt) and
[oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint).

Updates `@types/node` from 26.4.0 to 26.5.0
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node">compare
view</a></li>
</ul>
</details>
<br />

Updates `@types/react-dom` from 19.2.5 to 19.2.7
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom">compare
view</a></li>
</ul>
</details>
<br />

Updates `oxfmt` from 0.65.0 to 0.67.0
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/blob/main/npm/oxfmt/CHANGELOG.md">oxfmt's
changelog</a>.</em></p>
<blockquote>
<h2>[0.67.0] - 2026-09-07</h2>
<h3>🛡️ Security</h3>
<ul>
<li>9a612d5 deps: Update npm packages (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/26178">#26178</a>)
(renovate[bot])</li>
</ul>
<h2>[0.64.0] - 2026-08-18</h2>
<h3>🚀 Features</h3>
<ul>
<li>c07fe7c oxfmt: Support <code>experimentalOperatorPosition</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/25643">#25643</a>)
(leaysgur)</li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>fed6681 oxfmt: Skip expanding overrides options (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/25572">#25572</a>)
(leaysgur)</li>
</ul>
<h2>[0.62.0] - 2026-08-03</h2>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>e6e584b oxfmt: Type jsdoc. enum options (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/25008">#25008</a>)
(leaysgur)</li>
</ul>
<h2>[0.59.0] - 2026-07-13</h2>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>415fe1e oxfmt: Error on ignorePatterns that cannot match files
outside the config directory (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/24286">#24286</a>)
(leaysgur)</li>
</ul>
<h2>[0.55.0] - 2026-06-15</h2>
<h3>🚀 Features</h3>
<ul>
<li>9a2788b linter/unicorn: Implement <code>prefer-export-from</code>
rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/22935">#22935</a>)
(AliceLanniste)</li>
</ul>
<h2>[0.54.0] - 2026-06-08</h2>
<h3>📚 Documentation</h3>
<ul>
<li>dadafe3 oxlint, oxfmt: Mention migrate skills in npm READMEs (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/22965">#22965</a>)
(Boshen)</li>
<li>f88961a oxfmt: Annotate each config option with supported languages
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/22953">#22953</a>)
(leaysgur)</li>
</ul>
<h2>[0.52.0] - 2026-05-26</h2>
<h3>🚀 Features</h3>
<ul>
<li>16b8058 oxfmt: Support <code>vite-plus/resolveConfig</code> for
vite.config.ts (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/22454">#22454</a>)
(leaysgur)</li>
</ul>
<h2>[0.50.0] - 2026-05-15</h2>
<h3>🐛 Bug Fixes</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/commit/b4da00b621ec2f6f67ed218f5366c45ed325331b"><code>b4da00b</code></a>
release(apps): oxlint v1.82.0 &amp;&amp; oxfmt v0.67.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/26384">#26384</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/9a612d57e5f1ad9ee6f9f0d8d67692cd430f7ecf"><code>9a612d5</code></a>
chore(deps): update npm packages (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/26178">#26178</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/0b4e2e67f4193e7ebfcc64982275eb583ae82c83"><code>0b4e2e6</code></a>
release(apps): oxlint v1.81.0 &amp;&amp; oxfmt v0.66.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/26199">#26199</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/5ff57f83c049e338db8b2435dd92f660bfe19ed4"><code>5ff57f8</code></a>
fix(formatter/sort-imports): handle custom side effect groups (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/26217">#26217</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/63bc313934bf1fe94f8e0e6e59336f6036b8506e"><code>63bc313</code></a>
chore(npm): update funding URL (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/26066">#26066</a>)</li>
<li>See full diff in <a
href="https://github.com/oxc-project/oxc/commits/oxfmt_v0.67.0/npm/oxfmt">compare
view</a></li>
</ul>
</details>
<br />

Updates `oxlint` from 1.80.0 to 1.82.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/releases">oxlint's
releases</a>.</em></p>
<blockquote>
<h2>oxlint v1.82.0 &amp; oxfmt v0.67.0</h2>
<h2>Table of Contents</h2>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/blob/HEAD/#oxlint-v1.82.0">Oxlint
v1.82.0</a></li>
<li><a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/blob/HEAD/#oxfmt-v0.67.0">Oxfmt
v0.67.0</a></li>
</ul>
<h2>Oxlint v1.82.0</h2>
<h3>💥 BREAKING CHANGES</h3>
<ul>
<li>66744f0 parser: [<strong>BREAKING</strong>] Rename
<code>panicked</code> to <code>fatal_error</code> in
<code>ParserReturn</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26382">#26382</a>)
(overlookmotel)</li>
</ul>
<h3>🚀 Features</h3>
<ul>
<li>6a0e19c linter/eslint/no-unmodified-loop-condition: Support
<code>checkConditionalExpressions</code> option (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26249">#26249</a>)
(camc314)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>aa38ddf linter/unicorn/numeric-separators-style: Correct schema
defaults (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26393">#26393</a>)
(camc314)</li>
<li>562dd14 jsx-a11y/anchor-has-content: Allow anchors passed as custom
component props (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26360">#26360</a>)
(Mikhail Baev)</li>
<li>253cdb2 linter/eslint/id-length: Exempt TS interface/type-literal
members with never (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26310">#26310</a>)
(Pearce Ropion)</li>
<li>8c7ea76 ast_visit: Account for trimmed source offsets (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26223">#26223</a>)
(Cameron)</li>
<li>5f9cffc linter/typescript/no-explicit-any: Fix rest parameter to
<code>unknown[]</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26279">#26279</a>)
(camc314)</li>
<li>4de9fa3 linter/react/jsx-no-literals: Check nested JSX expression
literals (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26253">#26253</a>)
(camc314)</li>
<li>07851b9 parser: Fix debug assert failure when lexer error with
tokens enabled (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26229">#26229</a>)
(overlookmotel)</li>
<li>9cee146 oxlint/lsp: Don't try to parse fix message, always fallback
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26204">#26204</a>)
(Sysix)</li>
<li>a662c40 linter/eslint/no-useless-assignment: Handle destructuring
assignments (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25925">#25925</a>)
(camc314)</li>
<li>304bd9a linter/eslint/no-array-constructor: Handle ASI hazards in
fixer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26166">#26166</a>)
(Hamody We)</li>
<li>b41bb35 linter: Qualify rules in rules output (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26250">#26250</a>)
(camc314)</li>
<li>895c685 linter/typescript/no-extraneous-class: Avoid deleting class
expressions (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26231">#26231</a>)
(camc314)</li>
<li>4ba33c8 linter/eslint/no-unused-vars: Handle stored arrow references
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26225">#26225</a>)
(camc314)</li>
</ul>
<h3>🛡️ Security</h3>
<ul>
<li>9a612d5 deps: Update npm packages (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26178">#26178</a>)
(renovate[bot])</li>
</ul>
<h2>Oxfmt v0.67.0</h2>
<h3>💥 BREAKING CHANGES</h3>
<ul>
<li>66744f0 parser: [<strong>BREAKING</strong>] Rename
<code>panicked</code> to <code>fatal_error</code> in
<code>ParserReturn</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26382">#26382</a>)
(overlookmotel)</li>
<li>2c9a947 parser: [<strong>BREAKING</strong>] Reduce
<code>MAX_LEN</code> to 256 bytes below <code>u32::MAX</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26352">#26352</a>)
(overlookmotel)</li>
</ul>
<h3>🚀 Features</h3>
<ul>
<li>68e12ab formatter_css: Follow the oxc-css-parser's acceptance line
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26337">#26337</a>)
(leaysgur)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>f7acdc0 formatter: Treat a JSDoc cast target as opaque in chain
layouts (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26375">#26375</a>)
(leaysgur)</li>
<li>104061b formatter: Keep JSDoc cast parens with a comment inside them
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26374">#26374</a>)
(leaysgur)</li>
<li>3d00a76 formatter_css: Keep comment inside important and simple-vars
colon (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26370">#26370</a>)
(leaysgur)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md">oxlint's
changelog</a>.</em></p>
<blockquote>
<h2>[1.82.0] - 2026-09-07</h2>
<h3>🚀 Features</h3>
<ul>
<li>6a0e19c linter/eslint/no-unmodified-loop-condition: Support
<code>checkConditionalExpressions</code> option (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26249">#26249</a>)
(camc314)</li>
</ul>
<h2>[1.81.0] - 2026-08-31</h2>
<h3>📚 Documentation</h3>
<ul>
<li>d5be037 linter/typescript/switch-exhaustiveness-check: Clarify
default case comment pattern (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26100">#26100</a>)
(camc314)</li>
</ul>
<h2>[1.79.0] - 2026-08-18</h2>
<h3>💥 BREAKING CHANGES</h3>
<ul>
<li>8c4552d linter: [<strong>BREAKING</strong>] Split
react/react-compiler into per-category rules (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25500">#25500</a>)
(Boshen)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>228e8e0 linter: Resolve inactive React compiler rules (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25830">#25830</a>)
(Boshen)</li>
<li>aa49d86 linter: Allow spread rule options in config types (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25675">#25675</a>)
(ch3rry)</li>
<li>36f8451 linter/eslint/no-eval: Align indirect default with ESLint
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25656">#25656</a>)
(camc314)</li>
<li>beb724d linter/eslint/no-unused-vars: Report bare underscore
parameters (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25663">#25663</a>)
(camc314)</li>
<li>4004c10 linter/eslint/no-irregular-whitespace: Check comments by
default (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25660">#25660</a>)
(camc314)</li>
<li>285820e linter/no-large-snapshots: Precompile and document allowed
snapshot matchers (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25611">#25611</a>)
(Mikhail Baev)</li>
<li>4df5835 linter: Allow capitalized built-in calls (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25516">#25516</a>)
(Boshen)</li>
</ul>
<h2>[1.78.0] - 2026-08-10</h2>
<h3>🚀 Features</h3>
<ul>
<li>ccb8fe8 linter/jsdoc: Implement <code>no-blank-blocks</code> rule
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25207">#25207</a>)
(Mikhail Baev)</li>
<li>d4a897c linter/eslint: Implement <code>one-var</code> rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24470">#24470</a>)
(Cole Ellison)</li>
<li>5ab9340 linter/jsx-a11y/anchor-has-content: Add options to match
eslint (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24571">#24571</a>)
(Cole Ellison)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>9573937 linter/typescript: Validate <code>ban-ts-comment</code>
description_format (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25320">#25320</a>)
(Mikhail Baev)</li>
</ul>
<h2>[1.77.0] - 2026-08-03</h2>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>5c0fa61 linter/eslint/no-warning-comments: Unify config structs and
remove manual options docs (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25151">#25151</a>)
(Mikhail Baev)</li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>9dc7756 linter/typescript/no-unnecessary-condition: Clarify options
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25110">#25110</a>)
(camc314)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/commit/b4da00b621ec2f6f67ed218f5366c45ed325331b"><code>b4da00b</code></a>
release(apps): oxlint v1.82.0 &amp;&amp; oxfmt v0.67.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26384">#26384</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/aa38ddfbea657a9d332f86e42e235ff8cbdcac6d"><code>aa38ddf</code></a>
fix(linter/unicorn/numeric-separators-style): correct schema defaults
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26393">#26393</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/6a0e19cfaae35ec882de6bc8dde7668d1b0cc58e"><code>6a0e19c</code></a>
feat(linter/eslint/no-unmodified-loop-condition): support
`checkConditionalEx...</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/0b4e2e67f4193e7ebfcc64982275eb583ae82c83"><code>0b4e2e6</code></a>
release(apps): oxlint v1.81.0 &amp;&amp; oxfmt v0.66.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26199">#26199</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/d5be0375e6303202fcd6d34fa520103eceea0238"><code>d5be037</code></a>
docs(linter/typescript/switch-exhaustiveness-check): clarify default
case com...</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/63bc313934bf1fe94f8e0e6e59336f6036b8506e"><code>63bc313</code></a>
chore(npm): update funding URL (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26066">#26066</a>)</li>
<li>See full diff in <a
href="https://github.com/oxc-project/oxc/commits/oxlint_v1.82.0/npm/oxlint">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-11 02:07:23 -04:00
dependabot[bot] 9eb47989ec build(deps): bump the docker-base-image-digests group with 2 updates (#1328)
> [!WARNING]
> Cooldown could not be applied because no publication date was
available from the registry.
>

Bumps the docker-base-image-digests group with 2 updates: node and
[astral-sh/uv](https://github.com/astral-sh/uv).

Updates `node` from `e67514e` to `50c8e8c`

Updates `astral-sh/uv` from 0.12.9 to 0.12.13
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/uv/releases">astral-sh/uv's
releases</a>.</em></p>
<blockquote>
<h2>0.12.13</h2>
<h2>Release Notes</h2>
<p>Released on 2026-09-10.</p>
<h3>Python</h3>
<ul>
<li>Add GraalPy 3.13.0 (<a
href="https://redirect.github.com/astral-sh/uv/pull/21431">#21431</a>)</li>
</ul>
<h3>Enhancements</h3>
<ul>
<li>Verify hashes when downloading PEP 658 metadata sidecars (<a
href="https://redirect.github.com/astral-sh/uv/pull/21563">#21563</a>)</li>
</ul>
<h3>Preview features</h3>
<ul>
<li>Respect <code>ty</code> exclusions when <code>uv check</code>
automatically selects members of a virtual workspace (<a
href="https://redirect.github.com/astral-sh/uv/pull/21555">#21555</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Avoid full wheel downloads during resolution by reusing supported
hashes from direct URL fragments when metadata is available separately
(<a
href="https://redirect.github.com/astral-sh/uv/pull/21279">#21279</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Edit Windows entry-point launcher resources in memory to support
Nano Server and reduce antivirus contention (<a
href="https://redirect.github.com/astral-sh/uv/pull/18713">#18713</a>)</li>
<li>Prefer <code>core-metadata</code> over legacy aliases in JSON index
responses (<a
href="https://redirect.github.com/astral-sh/uv/pull/21563">#21563</a>)</li>
</ul>
<h2>Install uv 0.12.13</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://releases.astral.sh/github/uv/releases/download/0.12.13/uv-installer.sh
| sh
</code></pre>
<h3>Install prebuilt binaries via powershell script</h3>
<pre lang="sh"><code>powershell -ExecutionPolicy Bypass -c &quot;irm
https://releases.astral.sh/github/uv/releases/download/0.12.13/uv-installer.ps1
| iex&quot;
</code></pre>
<h2>Download uv 0.12.13</h2>
<table>
<thead>
<tr>
<th>File</th>
<th>Platform</th>
<th>Checksum</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.13/uv-aarch64-apple-darwin.tar.gz">uv-aarch64-apple-darwin.tar.gz</a></td>
<td>Apple Silicon macOS</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.13/uv-aarch64-apple-darwin.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.13/uv-x86_64-apple-darwin.tar.gz">uv-x86_64-apple-darwin.tar.gz</a></td>
<td>Intel macOS</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.13/uv-x86_64-apple-darwin.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.13/uv-aarch64-pc-windows-msvc.zip">uv-aarch64-pc-windows-msvc.zip</a></td>
<td>ARM64 Windows</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.13/uv-aarch64-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.13/uv-i686-pc-windows-msvc.zip">uv-i686-pc-windows-msvc.zip</a></td>
<td>x86 Windows</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.13/uv-i686-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.13/uv-x86_64-pc-windows-msvc.zip">uv-x86_64-pc-windows-msvc.zip</a></td>
<td>x64 Windows</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.13/uv-x86_64-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.13/uv-aarch64-unknown-linux-gnu.tar.gz">uv-aarch64-unknown-linux-gnu.tar.gz</a></td>
<td>ARM64 Linux</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.13/uv-aarch64-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
</tbody>
</table>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/uv/blob/main/CHANGELOG.md">astral-sh/uv's
changelog</a>.</em></p>
<blockquote>
<h2>0.12.13</h2>
<p>Released on 2026-09-10.</p>
<h3>Python</h3>
<ul>
<li>Add GraalPy 3.13.0 (<a
href="https://redirect.github.com/astral-sh/uv/pull/21431">#21431</a>)</li>
</ul>
<h3>Enhancements</h3>
<ul>
<li>Verify hashes when downloading PEP 658 metadata sidecars (<a
href="https://redirect.github.com/astral-sh/uv/pull/21563">#21563</a>)</li>
</ul>
<h3>Preview features</h3>
<ul>
<li>Respect <code>ty</code> exclusions when <code>uv check</code>
automatically selects members of a virtual workspace (<a
href="https://redirect.github.com/astral-sh/uv/pull/21555">#21555</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Avoid full wheel downloads during resolution by reusing supported
hashes from direct URL fragments when metadata is available separately
(<a
href="https://redirect.github.com/astral-sh/uv/pull/21279">#21279</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Edit Windows entry-point launcher resources in memory to support
Nano Server and reduce antivirus contention (<a
href="https://redirect.github.com/astral-sh/uv/pull/18713">#18713</a>)</li>
<li>Prefer <code>core-metadata</code> over legacy aliases in JSON index
responses (<a
href="https://redirect.github.com/astral-sh/uv/pull/21563">#21563</a>)</li>
</ul>
<h2>0.12.12</h2>
<p>Released on 2026-09-09.</p>
<p>The executables in our macOS and Windows release archives and
<code>uv</code> and <code>uv_build</code> wheels are now code-signed.
macOS executables are signed with an Apple Developer ID certificate and
notarized by Apple. Windows executables have timestamped Authenticode
signatures from Azure Artifact Signing. This enables verification of the
release publisher and binary integrity, supports publisher-based
allowlisting, and should reduce security warnings and antivirus false
positives.</p>
<h3>Bug fixes</h3>
<ul>
<li>Exclude distributions uploaded after the <code>exclude-newer</code>
cutoff from lockfiles and generated requirement hashes (<a
href="https://redirect.github.com/astral-sh/uv/pull/21539">#21539</a>)</li>
</ul>
<h2>0.12.11</h2>
<p>Released on 2026-09-08.</p>
<h3>Preview features</h3>
<ul>
<li>Generate missing artifact hashes when exporting
<code>pylock.toml</code> files to ensure they conform to PEP 751 (<a
href="https://redirect.github.com/astral-sh/uv/pull/20146">#20146</a>)</li>
<li>Warn when <code>pylock.toml</code> artifact hash tables are empty,
which will be rejected in a future uv release (<a
href="https://redirect.github.com/astral-sh/uv/pull/21462">#21462</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Speed up installs that overwrite existing files by eliminating
per-file temporary directories for atomic hard-link, symlink, and
reflink replacements (<a
href="https://redirect.github.com/astral-sh/uv/pull/21478">#21478</a>)</li>
<li>Speed up installs that merge copied wheels into existing
environments by replacing per-file temporary directories with adjacent
temporary files (<a
href="https://redirect.github.com/astral-sh/uv/pull/21468">#21468</a>)</li>
<li>Speed up local wheel installs by replacing the shared ZIP cursor
lock with positioned reads (<a
href="https://redirect.github.com/astral-sh/uv/pull/21500">#21500</a>)</li>
<li>Speed up local wheel installs by reusing ZIP readers and buffers
across extracted files (<a
href="https://redirect.github.com/astral-sh/uv/pull/21499">#21499</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/uv/commit/0ebbd9274a55a8a53a13970be3b97e4209598e17"><code>0ebbd92</code></a>
Bump version to 0.12.13 (<a
href="https://redirect.github.com/astral-sh/uv/issues/21594">#21594</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/17ccae34815e249c66e53abe21916c3c0b6413e5"><code>17ccae3</code></a>
Use separate Depot projects for Docker development builds (<a
href="https://redirect.github.com/astral-sh/uv/issues/21591">#21591</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/8c18e15bd36dff0e17509cc68c3721798ae1ec2b"><code>8c18e15</code></a>
Use the workflow revision for PR security review configuration (<a
href="https://redirect.github.com/astral-sh/uv/issues/21592">#21592</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/9ebb1f6a72db257a689049ee060b7cf93638aada"><code>9ebb1f6</code></a>
Clarify release pipeline job names (<a
href="https://redirect.github.com/astral-sh/uv/issues/21561">#21561</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/d87450d82b3941507e48ac8b57f72a9d00709289"><code>d87450d</code></a>
Omit unused <code>exclude-newer-package</code> entries from script locks
(<a
href="https://redirect.github.com/astral-sh/uv/issues/21589">#21589</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/911f3a208c104b07b2f4da3a2283564d9a95c403"><code>911f3a2</code></a>
Refactor release artifact handling (<a
href="https://redirect.github.com/astral-sh/uv/issues/21556">#21556</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/a712e811024933ec441667659d6df73c04f5b678"><code>a712e81</code></a>
Separate Docker release builds from publishing (<a
href="https://redirect.github.com/astral-sh/uv/issues/21586">#21586</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/4196dae60a0cc542bd43c0188f8b4c6dbfa0df98"><code>4196dae</code></a>
Use xhigh effort for PR security review (<a
href="https://redirect.github.com/astral-sh/uv/issues/21530">#21530</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/a51624b4791456260f72068ae878796233f72809"><code>a51624b</code></a>
Reduce PR security review reporting work (<a
href="https://redirect.github.com/astral-sh/uv/issues/21528">#21528</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/63e28b69821b4b55b90287d799cbf97ff6ba0f74"><code>63e28b6</code></a>
Use <code>editpe</code> for trampoline resource edits (<a
href="https://redirect.github.com/astral-sh/uv/issues/18713">#18713</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/uv/compare/0.12.9...0.12.13">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-11 02:07:12 -04:00
Austin Brogle 99e0cfde3d fix: prevent Anna's Archive download countdown resets by preserving browser sessions (#1325)
## Observed bug

Anna's Archive slow-download pages can return a JavaScript countdown
before a download link is available. The internal browser returns that
waiting-room HTML and closes its incognito session. The downloader then
sleeps and fetches the URL again, which can create a new queue session
and **restart the countdown instead of reaching the download link**.

## Fix

- **Preserve the queue session:** keep the original browser tab open
while the site's own countdown and automatic navigation finish. HTTP 200
and cached-cookie waiting-room responses enter the same flow.
- **Return a consistent page:** capture HTML and readiness together in
one browser evaluation so navigation cannot pair a new page's status
with stale protection-page HTML. Share cache validation and
page-readiness rules across their callers.
- **Keep waiting cancellable and bounded:** poll cancellation while a
slow browser read remains pending, rather than repeatedly cancelling and
reissuing it. Apply a **300-second waiting-room limit** within the
existing browser watchdog.
- **Report queue timeouts accurately:** preserve the timeout across the
helper-process boundary and stop the solve without restarting the
browser or rotating mirrors.

Waiting-room detection is limited to Anna's Archive `/slow_download/`
pages containing an actual `.js-partner-countdown` element. The site
controls the countdown and refresh. External-bypasser behavior and
file-transfer timeouts are unchanged; the PR adds no deployment
configuration or dependencies.

## Validation

Validated at `c81e0a2`:

| Check | Result |
| --- | --- |
| Full Linux unit suite | **2,978 passed** on Python 3.14 in a non-root
environment with entrypoint test stubs enabled |
| Focused regression coverage | **30 passed**, covering countdown
completion, zero timers, navigation, both cookie-cache paths,
cancellation, stuck queues, slow reads, and timeout propagation |
| Navigation-race regression | Fails against the previous PR
implementation and passes with the fix |
| Python static checks | Ruff lint/format, BasedPyright for backend and
tests, and Vulture passed |
| Real Chromium fixture | Queue cookie persisted through 1.5-second DOM
reads and one automatic refresh; the CDP connection survived multiple
polling intervals |
| Live source check | Observed **19 → 14 → 9 → 4 → download link** while
retaining the browser session; the patched browser path also completed
the waiting room |

Full unit-suite command:

```sh
pytest tests/ -n 2 --tb=short -m "not integration and not e2e"
```

The live check validates waiting-room completion and link resolution.
Remote file-host availability remains a separate concern. The unit suite
emitted two existing Authlib deprecation warnings.
2026-09-11 00:51:48 -04:00
Jorge Lima c45d342931 fix(prowlarr): skip indexers in Prowlarr failure back-off (#1324)
## What

Read `/api/v1/indexerstatus` once per search and skip indexers whose
`disabledTill` is still ahead. Skipped is neither attempted nor failed.
One client method, one counter on `_IndexerSearchOutcome`, ten tests.

## Why

Prowlarr's own search leaves out an indexer it has disabled after
repeated failures. Shelfmark queries each indexer through its Torznab
endpoint, which answers 429 instead:

```
Prowlarr Torznab error response: <error code="429" description="Indexer is disabled till 09/09/2026 15:00:34 due to recent failures." />
Prowlarr: 1 of 5 indexer searches failed (indexer 2 search failed: 429 Client Error: Too Many Requests ...)
Release search failed for source prowlarr: 1 of 5 indexer searches failed (...)
```

That counted as a failed search, so with one indexer in back-off and the
other four answering empty, `/api/releases?source=prowlarr` returned 503
for every book for the length of the back-off (one hour here).

`/api/v1/indexerstatus` on Prowlarr 2.5.2:

```json
[{"indexerId": 2, "disabledTill": "2026-09-09T15:00:34Z", "mostRecentFailure": "2026-09-09T14:00:34Z", "initialFailure": "2026-09-09T14:00:34Z"}]
```

## Behaviour

| indexers | before | after |
|---|---|---|
| 1 in back-off, 4 answer empty | 503 "1 of 5 indexer searches failed" |
"No releases found" |
| 1 in back-off, 1 answers with releases | releases | releases, one
Torznab call fewer |
| 1 in back-off, 1 times out, 3 answer empty | "1 of 5 failed" | "1 of 4
failed" |
| every indexer in back-off | 503 "5 of 5 failed" | "every indexer is
disabled by Prowlarr after recent failures (until ...)" |
| status endpoint unreachable | n/a | as before, nothing skipped |

Auto-expand no longer retries a pass in which nothing was asked.

## Tests

`uv run pytest tests/prowlarr`: 563 passed, 42 skipped. `ruff check` and
`ruff format` clean.
2026-09-11 00:49:33 -04:00
oliver 1e3fd48b8b fix: share rotating log file handlers (#1316)
This patch shares (for each log file) the `RotatingFileHandler` for
logging across all modules, reducing the number of open file descriptors
from ~78 to 1.

I had originally assumed this issue was a resource leak, but it seems to
just be a large fixed number of file descriptors. So this change mostly
just (1) shrinks the number of open file descriptors to a reasonable
level and (2) prevents two modules in the same process competing to
write to a log file.
2026-09-11 00:48:22 -04:00
Vinicius GabrielandClaude Sonnet 5 c576003319 feat(naming): add {FirstAuthor} template token (#1322)
Closes #930.

## What

New `{FirstAuthor}` naming-template token. It renders only the first
author when metadata lists several ("Author1, Author2, Author3"), so
multi-author books can be filed alongside the rest of that author's work
instead of getting their own "Author1, Author2, ..." folder.

```
{Author}       -> Terry Pratchett, Neil Gaiman
{FirstAuthor}  -> Terry Pratchett
```

## How

- Added to `KNOWN_TOKENS` in `shelfmark/core/naming.py`, positioned
before `author` so `{FirstAuthor}` isn't parsed as literal `First` +
`{Author}`.
- Derived inside `parse_naming_template` from the existing `Author`
value (split on `,` / `;`), so every caller — folder transfer, rename,
the settings preview — picks it up with no extra wiring. An explicit
`FirstAuthor` key in the metadata still wins if one is ever passed.
- `{Author}` behaviour is unchanged.
- Frontend `namingTemplatePreview.ts` token list + `KNOWN_TOKENS` kept
in lockstep (there's a test enforcing that), with a matching
`firstAuthor` helper.
- Settings field descriptions + `docs/environment-variables.md` list the
new token.

## Known limitation

A lone author written `Last, First` is split on the comma too and
renders as `Last` — the source metadata doesn't mark which form it is.
Called out in the token help text and covered by a test. `{Author}`
remains available for anyone who wants the raw string.

## Checks

- `make python-test` — 2963 passed
- `make python-lint` / `make python-format` / `make python-typecheck` /
vulture — clean
- `make frontend-test` — 187 passed · `frontend-lint` /
`frontend-format` / `frontend-typecheck` — clean

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 00:47:37 -04:00
Atirna 265da07d7f feat(download): add Blackhole torrent handoff (#1312)
## Why

Blackhole users need Shelfmark to hand a torrent file to their existing
downloader instead of importing the downloaded book itself.

## Change

- add Blackhole as a torrent client with a configurable watched
directory
- prefer a fetched `.torrent` file for Blackhole while preserving magnet
preference for other clients
- complete the queue task after the handoff without invoking book
post-processing

## Verification

- `uv run pytest -q tests/prowlarr/test_blackhole_client.py
tests/prowlarr/test_handler.py tests/newznab/test_handler.py
tests/download/test_orchestrator_lifecycle.py`
- `uv run basedpyright shelfmark/download/clients/blackhole.py
shelfmark/download/clients/__init__.py
shelfmark/download/clients/base_handler.py
shelfmark/download/clients/settings.py
shelfmark/download/orchestrator.py shelfmark/release_sources/__init__.py
shelfmark/release_sources/prowlarr/utils.py
shelfmark/release_sources/prowlarr/handler.py
shelfmark/release_sources/newznab/handler.py
tests/prowlarr/test_blackhole_client.py tests/prowlarr/test_handler.py
tests/newznab/test_handler.py
tests/download/test_orchestrator_lifecycle.py`

Fixes #1229
2026-09-11 00:46:47 -04:00
Nicholas Velten 96d1b7c33a Keep default filters out of the URL hash (#1314)
Follow up to #1311, per your "no use keeping empty / default values in
the url".

A plain author search was coming out as

```
#q=herbert&search_by=author&sort=relevance&lang=default&format=epub&format=mobi&format=azw3&format=fb2&format=djvu&format=cbz&format=cbr
```

The builder serialized every advanced filter regardless of whether the
user had touched it. Now `sort` drops when it equals the sort the app
would apply anyway (the provider default in Universal, the configured
one in Direct), `lang` drops while it is still the `default` sentinel,
and `format` drops when the selection matches `supported_formats` from
the config. Formats are compared as a set, so reselecting everything in
a different order still counts as default. Anything the user actually
changed stays.

That also makes the comment I left in the builder true: a default-state
URL now carries no hash at all.

The second commit is an ordering bug that omitting defaults made
visible. `loadConfig`'s `initial` branch seeds `formats` from
`supported_formats` and `sort` from the mode default. The URL bootstrap
is gated on config being loaded, so it normally runs after that and wins
on its own, but nothing guarantees only one `initial` load happens, and
a second one landing after the bootstrap resets `formats` to the full
supported list and drops the sort the link asked for. StrictMode
double-invokes the mount effect that triggers it, so it reproduces in
development: `#q=dune&format=epub&lang=en` intermittently loses its
`format=epub`. The seeding is now skipped once the bootstrap has
applied, so a link's filters win over the defaults they were meant to
override.

Five new unit tests on the builder. I also drove both the dev server and
a production build in Chromium with `/api` mocked:
`#search_by=author&q=herbert` settles at `#q=herbert&search_by=author`,
typing a plain query gives `#q=dune`, direct mode at rest carries no
hash, and `#q=dune&format=epub&lang=en` keeps both filters in the hash
and in the request. That last one was 3 for 5 on the dev server before
the second commit and 5 for 5 after.
2026-09-11 00:36:55 -04:00
dependabot[bot] 6addae9d7c build(deps): bump the python-deps group across 1 directory with 4 updates (#1327)
Bumps the python-deps group with 4 updates in the / directory:
[seleniumbase](https://github.com/seleniumbase/SeleniumBase),
[basedpyright](https://github.com/detachhead/basedpyright),
[prek](https://github.com/j178/prek) and
[ruff](https://github.com/astral-sh/ruff).

Updates `seleniumbase` from 4.53.5 to 4.53.7
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.53.7 - MCP Server: Patch 7</h2>
<h2>MCP Server: Patch 7</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/698672b1d14d397c8fabef17c14c36efb01047fa">Update
the MCP Server</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/f5c40a2de161745c0de5130ad7479dc823aaf08e">Refresh
Python dependencies</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>MCP Server: Patch 7 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4493">seleniumbase/SeleniumBase#4493</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.53.6...v4.53.7">https://github.com/seleniumbase/SeleniumBase/compare/v4.53.6...v4.53.7</a></p>
<h2>4.53.6 - MCP Server: Patch 6</h2>
<h2>MCP Server: Patch 6</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/b477cb359f466e5d6f712c6c30a03150076e1420">Update
the MCP server</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>MCP Server: Patch 6 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4491">seleniumbase/SeleniumBase#4491</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.53.5...v4.53.6">https://github.com/seleniumbase/SeleniumBase/compare/v4.53.5...v4.53.6</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/4ee7dfc4ae83c19385f5ac129f2cda0cfa863d80"><code>4ee7dfc</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4493">#4493</a>
from seleniumbase/mcp-server-patch-7</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/a9c9253eec504274d3dbe19202ec858201fe4fd8"><code>a9c9253</code></a>
Version 4.53.7</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/f5c40a2de161745c0de5130ad7479dc823aaf08e"><code>f5c40a2</code></a>
Refresh Python dependencies</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/698672b1d14d397c8fabef17c14c36efb01047fa"><code>698672b</code></a>
Update the MCP Server</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/db0d4041dde54142a2dd9810b23b30b6aa40fd97"><code>db0d404</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4491">#4491</a>
from seleniumbase/mcp-server-patch-6</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/7b835cfbad6becff033a9ae059b84775f42a4e2e"><code>7b835cf</code></a>
Version 4.53.6</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/b477cb359f466e5d6f712c6c30a03150076e1420"><code>b477cb3</code></a>
Update the MCP server</li>
<li>See full diff in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.53.5...v4.53.7">compare
view</a></li>
</ul>
</details>
<br />

Updates `basedpyright` from 1.39.10 to 1.40.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/detachhead/basedpyright/releases">basedpyright's
releases</a>.</em></p>
<blockquote>
<h2>v1.40.0 (pyright 1.1.412)</h2>
<h2>What's Changed</h2>
<ul>
<li>drop support for python 3.8 and 3.9 in the pypi package, which are
EOL by <a
href="https://github.com/DetachHead"><code>@​DetachHead</code></a> in <a
href="https://redirect.github.com/DetachHead/basedpyright/pull/1876">DetachHead/basedpyright#1876</a>
<ul>
<li>users who still need to use unsupported Python versions can still
install <a href="https://npmjs.com/basedpyright">the basedpyright npm
package</a></li>
</ul>
</li>
<li>fix rendering of newlines for builtin docstrings by <a
href="https://github.com/tommyming"><code>@​tommyming</code></a> in <a
href="https://redirect.github.com/DetachHead/basedpyright/pull/1871">DetachHead/basedpyright#1871</a></li>
<li>migrate the docs site to zensical by <a
href="https://github.com/DetachHead"><code>@​DetachHead</code></a> in <a
href="https://redirect.github.com/DetachHead/basedpyright/pull/1795">DetachHead/basedpyright#1795</a></li>
<li>move locale env variable docs to the more appropriate
&quot;Environment Variables&quot; page by <a
href="https://github.com/DetachHead"><code>@​DetachHead</code></a> in <a
href="https://redirect.github.com/DetachHead/basedpyright/pull/1878">DetachHead/basedpyright#1878</a></li>
<li>Support new Python Environments API when enabled by <a
href="https://github.com/Weidav"><code>@​Weidav</code></a> in <a
href="https://redirect.github.com/DetachHead/basedpyright/pull/1860">DetachHead/basedpyright#1860</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/tommyming"><code>@​tommyming</code></a>
made their first contribution in <a
href="https://redirect.github.com/DetachHead/basedpyright/pull/1871">DetachHead/basedpyright#1871</a></li>
<li><a href="https://github.com/Weidav"><code>@​Weidav</code></a> made
their first contribution in <a
href="https://redirect.github.com/DetachHead/basedpyright/pull/1860">DetachHead/basedpyright#1860</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/DetachHead/basedpyright/compare/v1.39.10...v1.40.0">https://github.com/DetachHead/basedpyright/compare/v1.39.10...v1.40.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/d64b2274480bc64c146f5b387127d4bb35eb403e"><code>d64b227</code></a>
1.40.0</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/1a9ec37d0cffde93059b5993fb094f7dc9e91242"><code>1a9ec37</code></a>
Support new Python Environments API when enabled (<a
href="https://redirect.github.com/detachhead/basedpyright/issues/1860">#1860</a>)</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/37ab66d1bcc6c3e00b391f50b1da9a313e785a96"><code>37ab66d</code></a>
move locale env variable docs to the more appropriate &quot;Environment
Variables&quot;...</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/db9be3ceda3b101695574b24761e1bf42b0c5701"><code>db9be3c</code></a>
zensical (<a
href="https://redirect.github.com/detachhead/basedpyright/issues/1795">#1795</a>)</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/f408bd7ab1adb419fd1986ccc3a79723e29f4c8e"><code>f408bd7</code></a>
fix rendering of newlines for builtin docstrings (<a
href="https://redirect.github.com/detachhead/basedpyright/issues/1871">#1871</a>)</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/0310a8d3cf6346978ea511e4fb9b1d857fc837d8"><code>0310a8d</code></a>
drop support for python 3.8 and 3.9 in the pypi package, which are
EOL.</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/8a06544547fd26b32fe0f3ff334ce125ba8ca5e7"><code>8a06544</code></a>
bump gg deps and python deps</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/01ffa7fa80ece700dc0e824de7da761f6554e5fa"><code>01ffa7f</code></a>
bump gg to fix download indicator interfering with primer output</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/d5db35dd53d3af48809e5d04e1d0714113363092"><code>d5db35d</code></a>
workaround gg download progress indicator interfering with primer
output</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/dc53c61a95a5d60a348033ed4fba09e3f73d7cba"><code>dc53c61</code></a>
fix primer</li>
<li>Additional commits viewable in <a
href="https://github.com/detachhead/basedpyright/compare/v1.39.10...v1.40.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `prek` from 0.5.0 to 0.5.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/releases">prek's
releases</a>.</em></p>
<blockquote>
<h2>0.5.2</h2>
<h2>Release Notes</h2>
<p>Released on 2026-09-03.</p>
<h3>Enhancements</h3>
<ul>
<li>Allow unknown tags by default in <code>check-yaml</code> (<a
href="https://redirect.github.com/j178/prek/pull/2678">#2678</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/j178"><code>@​j178</code></a></li>
</ul>
<h2>Install prek 0.5.2</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://github.com/j178/prek/releases/download/v0.5.2/prek-installer.sh
| sh
</code></pre>
<h3>Install prebuilt binaries via powershell script</h3>
<pre lang="sh"><code>powershell -ExecutionPolicy Bypass -c &quot;irm
https://github.com/j178/prek/releases/download/v0.5.2/prek-installer.ps1
| iex&quot;
</code></pre>
<h3>Install prebuilt binaries via Homebrew</h3>
<pre lang="sh"><code>brew install prek
</code></pre>
<h2>Download prek 0.5.2</h2>
<table>
<thead>
<tr>
<th>File</th>
<th>Platform</th>
<th>Checksum</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.2/prek-aarch64-apple-darwin.tar.gz">prek-aarch64-apple-darwin.tar.gz</a></td>
<td>Apple Silicon macOS</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.2/prek-aarch64-apple-darwin.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.2/prek-x86_64-apple-darwin.tar.gz">prek-x86_64-apple-darwin.tar.gz</a></td>
<td>Intel macOS</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.2/prek-x86_64-apple-darwin.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.2/prek-aarch64-pc-windows-msvc.zip">prek-aarch64-pc-windows-msvc.zip</a></td>
<td>ARM64 Windows</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.2/prek-aarch64-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.2/prek-x86_64-pc-windows-msvc.zip">prek-x86_64-pc-windows-msvc.zip</a></td>
<td>x64 Windows</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.2/prek-x86_64-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.2/prek-aarch64-unknown-linux-gnu.tar.gz">prek-aarch64-unknown-linux-gnu.tar.gz</a></td>
<td>ARM64 Linux</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.2/prek-aarch64-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.2/prek-x86_64-unknown-linux-gnu.tar.gz">prek-x86_64-unknown-linux-gnu.tar.gz</a></td>
<td>x64 Linux</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.2/prek-x86_64-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.2/prek-aarch64-unknown-linux-musl.tar.gz">prek-aarch64-unknown-linux-musl.tar.gz</a></td>
<td>ARM64 MUSL Linux</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.2/prek-aarch64-unknown-linux-musl.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.2/prek-x86_64-unknown-linux-musl.tar.gz">prek-x86_64-unknown-linux-musl.tar.gz</a></td>
<td>x64 MUSL Linux</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.5.2/prek-x86_64-unknown-linux-musl.tar.gz.sha256">checksum</a></td>
</tr>
</tbody>
</table>
<h2>Verifying GitHub Artifact Attestations</h2>
<p>The artifacts in this release have attestations generated with GitHub
Artifact Attestations. These can be verified by using the <a
href="https://cli.github.com/manual/gh_attestation_verify">GitHub
CLI</a>:</p>
<pre lang="sh"><code>&lt;/tr&gt;&lt;/table&gt; 
</code></pre>
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/blob/master/CHANGELOG.md">prek's
changelog</a>.</em></p>
<blockquote>
<h2>0.5.2</h2>
<p>Released on 2026-09-03.</p>
<h3>Enhancements</h3>
<ul>
<li>Allow unknown tags by default in <code>check-yaml</code> (<a
href="https://redirect.github.com/j178/prek/pull/2678">#2678</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/j178"><code>@​j178</code></a></li>
</ul>
<h2>0.5.1</h2>
<p>Released on 2026-09-01.</p>
<h3>Enhancements</h3>
<ul>
<li>Add <code>--hide-status &lt;passed|failed|skipped&gt;</code> for
hook reports (<a
href="https://redirect.github.com/j178/prek/pull/2644">#2644</a>)</li>
<li>Add <code>prek init</code> for repository setup (<a
href="https://redirect.github.com/j178/prek/pull/2636">#2636</a>)</li>
<li>Apply hook <code>env</code> during environment creation (<a
href="https://redirect.github.com/j178/prek/pull/2650">#2650</a>)</li>
<li>Disable error snippets in <code>check-yaml</code> diagnostics (<a
href="https://redirect.github.com/j178/prek/pull/2664">#2664</a>)</li>
<li>Show hooks excluded by skip selectors (<a
href="https://redirect.github.com/j178/prek/pull/2645">#2645</a>)</li>
<li>Support Pixi for Conda environments (<a
href="https://redirect.github.com/j178/prek/pull/2667">#2667</a>)</li>
<li>Support <code>cargo-binstall</code> for Rust CLI dependencies (<a
href="https://redirect.github.com/j178/prek/pull/2658">#2658</a>)</li>
<li>Warn about unused keys in user settings (<a
href="https://redirect.github.com/j178/prek/pull/2665">#2665</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Reject unsupported YAML tags in check-yaml (<a
href="https://redirect.github.com/j178/prek/pull/2656">#2656</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Clarify local hook documentation (<a
href="https://redirect.github.com/j178/prek/pull/2640">#2640</a>)</li>
<li>Clarify pre-commit command compatibility (<a
href="https://redirect.github.com/j178/prek/pull/2635">#2635</a>)</li>
<li>Document automatic PR fixes with autofix.ci (<a
href="https://redirect.github.com/j178/prek/pull/2643">#2643</a>)</li>
<li>Document check-yaml unsafe support (<a
href="https://redirect.github.com/j178/prek/pull/2632">#2632</a>)</li>
<li>Improve setup and workflow documentation (<a
href="https://redirect.github.com/j178/prek/pull/2637">#2637</a>)</li>
</ul>
<h3>Other changes</h3>
<ul>
<li>Drop low-usage release targets (<a
href="https://redirect.github.com/j178/prek/pull/2651">#2651</a>)</li>
<li>Generate a prek manifest JSON schema (<a
href="https://redirect.github.com/j178/prek/pull/2648">#2648</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/j178"><code>@​j178</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/j178/prek/commit/bfaa03cc998a43b1092e7ea918d0c9437a3ade64"><code>bfaa03c</code></a>
Bump version to 0.5.2 (<a
href="https://redirect.github.com/j178/prek/issues/2679">#2679</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/f0ce4c249ef17f9be0162e3c42ed765d073619ff"><code>f0ce4c2</code></a>
Allow unknown tags by default in <code>check-yaml</code> (<a
href="https://redirect.github.com/j178/prek/issues/2678">#2678</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/78bb3fe7d901289b3f45c5a5280147860d87cf30"><code>78bb3fe</code></a>
Show verbose builtin details by default (<a
href="https://redirect.github.com/j178/prek/issues/2676">#2676</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/57ea73d2ac28ae8d13fdf25c98c9b4b092a64945"><code>57ea73d</code></a>
Add <code>--allow-unknown-tags</code> to <code>check-yaml</code> to
disable tag checks (<a
href="https://redirect.github.com/j178/prek/issues/2675">#2675</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/695f3d0d7f113ccbbcad6555103107d6866e6b28"><code>695f3d0</code></a>
Show hook selectors first in prek run help (<a
href="https://redirect.github.com/j178/prek/issues/2673">#2673</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/edf43d5014da3566f5ea2f4bdee4f578f9381386"><code>edf43d5</code></a>
Reapply &quot;Reject unsupported YAML tags in check-yaml (<a
href="https://redirect.github.com/j178/prek/issues/2656">#2656</a>)&quot;
(<a
href="https://redirect.github.com/j178/prek/issues/2672">#2672</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/ed225b182dba642745ee1f79e185b6afa1928420"><code>ed225b1</code></a>
Bump version to 0.5.2 (<a
href="https://redirect.github.com/j178/prek/issues/2671">#2671</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/de6e61481c610de0836b2628407bfaa353aa3fcd"><code>de6e614</code></a>
Revert unsupported YAML tag rejection in check-yaml (<a
href="https://redirect.github.com/j178/prek/issues/2670">#2670</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/10a896eb63d60dd1175cc7b10ff0798e7070e56f"><code>10a896e</code></a>
Bump version to 0.5.1 (<a
href="https://redirect.github.com/j178/prek/issues/2669">#2669</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/864870940e0d246aa2ce9c3a9a693092e3f8002f"><code>8648709</code></a>
Support Pixi for Conda environments (<a
href="https://redirect.github.com/j178/prek/issues/2667">#2667</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/j178/prek/compare/v0.5.0...v0.5.2">compare
view</a></li>
</ul>
</details>
<br />

Updates `ruff` from 0.16.5 to 0.16.6
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/releases">ruff's
releases</a>.</em></p>
<blockquote>
<h2>0.16.6</h2>
<h2>Release Notes</h2>
<p>Released on 2026-09-03.</p>
<h3>Preview features</h3>
<ul>
<li>Move <code>pytest-fixture-autouse</code> to the
<code>restriction</code> category (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28219">#28219</a>)</li>
<li>[<code>flake8-pytest-style</code>] Add an autofix for
<code>PT020</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27993">#27993</a>)</li>
<li>[<code>flake8-tidy-imports</code>] Prevent fix loop between
<code>TID254</code> and <code>TID255</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28262">#28262</a>)</li>
<li>[<code>isort</code>] Exclude pragma comments from line length
calculation (<code>I001</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27313">#27313</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Validate unary expressions when parsing (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28233">#28233</a>)</li>
<li>[<code>flake8-async</code>, <code>pylint</code>] Recognize
<code>builtins.open</code> (<code>ASYNC230</code>, <code>PLW1514</code>)
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/28021">#28021</a>)</li>
<li>[<code>flake8-bugbear</code>] Fix panic on <code>match</code>
subjects (<code>B031</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27781">#27781</a>)</li>
<li>[<code>flake8-datetimez</code>] Reject <code>tzinfo=None</code> for
<code>datetime</code> bounds (<code>DTZ901</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28022">#28022</a>)</li>
<li>[<code>flake8-pytest-style</code>] Avoid duplicate
<code>PT017</code> diagnostics (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27918">#27918</a>)</li>
<li>[<code>ruff</code>] Remove <code>lint.external</code> hint for
Ruff-specific suppressions (<code>RUF102</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27923">#27923</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>flake8-use-pathlib</code>] Add display-only fix for
<code>os.listdir</code> (<code>PTH208</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28027">#28027</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Add another example and glob reference for
<code>lint.per-file-ignores</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28106">#28106</a>)</li>
<li>Add duplicate work guidance (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28229">#28229</a>)</li>
<li>[<code>flake8-async</code>] Document thread offloading
(<code>ASYNC240</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28008">#28008</a>)</li>
<li>[<code>pyupgrade</code>] Clarify default <code>encoding</code>
argument handling (<code>UP012</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27315">#27315</a>)</li>
</ul>
<h3>Other changes</h3>
<ul>
<li>Allow unary plus in match patterns on Python 3.15 (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28231">#28231</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/flying-sheep"><code>@​flying-sheep</code></a></li>
<li><a
href="https://github.com/pikammmmm"><code>@​pikammmmm</code></a></li>
<li><a href="https://github.com/fly1d"><code>@​fly1d</code></a></li>
<li><a
href="https://github.com/waterWang"><code>@​waterWang</code></a></li>
<li><a
href="https://github.com/Pierre-Sassoulas"><code>@​Pierre-Sassoulas</code></a></li>
<li><a href="https://github.com/ntBre"><code>@​ntBre</code></a></li>
<li><a
href="https://github.com/jelle-openai"><code>@​jelle-openai</code></a></li>
<li><a
href="https://github.com/ericbuehl"><code>@​ericbuehl</code></a></li>
<li><a href="https://github.com/tjkuson"><code>@​tjkuson</code></a></li>
<li><a
href="https://github.com/AbhinavMir"><code>@​AbhinavMir</code></a></li>
<li><a
href="https://github.com/Whning0513"><code>@​Whning0513</code></a></li>
<li><a
href="https://github.com/chirizxc"><code>@​chirizxc</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's
changelog</a>.</em></p>
<blockquote>
<h2>0.16.6</h2>
<p>Released on 2026-09-03.</p>
<h3>Preview features</h3>
<ul>
<li>Move <code>pytest-fixture-autouse</code> to the
<code>restriction</code> category (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28219">#28219</a>)</li>
<li>[<code>flake8-pytest-style</code>] Add an autofix for
<code>PT020</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27993">#27993</a>)</li>
<li>[<code>flake8-tidy-imports</code>] Prevent fix loop between
<code>TID254</code> and <code>TID255</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28262">#28262</a>)</li>
<li>[<code>isort</code>] Exclude pragma comments from line length
calculation (<code>I001</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27313">#27313</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Validate unary expressions when parsing (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28233">#28233</a>)</li>
<li>[<code>flake8-async</code>, <code>pylint</code>] Recognize
<code>builtins.open</code> (<code>ASYNC230</code>, <code>PLW1514</code>)
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/28021">#28021</a>)</li>
<li>[<code>flake8-bugbear</code>] Fix panic on <code>match</code>
subjects (<code>B031</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27781">#27781</a>)</li>
<li>[<code>flake8-datetimez</code>] Reject <code>tzinfo=None</code> for
<code>datetime</code> bounds (<code>DTZ901</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28022">#28022</a>)</li>
<li>[<code>flake8-pytest-style</code>] Avoid duplicate
<code>PT017</code> diagnostics (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27918">#27918</a>)</li>
<li>[<code>ruff</code>] Remove <code>lint.external</code> hint for
Ruff-specific suppressions (<code>RUF102</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27923">#27923</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>flake8-use-pathlib</code>] Add display-only fix for
<code>os.listdir</code> (<code>PTH208</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28027">#28027</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Add another example and glob reference for
<code>lint.per-file-ignores</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28106">#28106</a>)</li>
<li>Add duplicate work guidance (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28229">#28229</a>)</li>
<li>[<code>flake8-async</code>] Document thread offloading
(<code>ASYNC240</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28008">#28008</a>)</li>
<li>[<code>pyupgrade</code>] Clarify default <code>encoding</code>
argument handling (<code>UP012</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27315">#27315</a>)</li>
</ul>
<h3>Other changes</h3>
<ul>
<li>Allow unary plus in match patterns on Python 3.15 (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28231">#28231</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/flying-sheep"><code>@​flying-sheep</code></a></li>
<li><a
href="https://github.com/pikammmmm"><code>@​pikammmmm</code></a></li>
<li><a href="https://github.com/fly1d"><code>@​fly1d</code></a></li>
<li><a
href="https://github.com/waterWang"><code>@​waterWang</code></a></li>
<li><a
href="https://github.com/Pierre-Sassoulas"><code>@​Pierre-Sassoulas</code></a></li>
<li><a href="https://github.com/ntBre"><code>@​ntBre</code></a></li>
<li><a
href="https://github.com/jelle-openai"><code>@​jelle-openai</code></a></li>
<li><a
href="https://github.com/ericbuehl"><code>@​ericbuehl</code></a></li>
<li><a href="https://github.com/tjkuson"><code>@​tjkuson</code></a></li>
<li><a
href="https://github.com/AbhinavMir"><code>@​AbhinavMir</code></a></li>
<li><a
href="https://github.com/Whning0513"><code>@​Whning0513</code></a></li>
<li><a
href="https://github.com/chirizxc"><code>@​chirizxc</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ruff/commit/22f65a2ab5052990503985c7c794de37598d531e"><code>22f65a2</code></a>
Bump 0.16.6 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28280">#28280</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/7cc9f1ed53e5ce175f17fde3a75fe792ac41e8cd"><code>7cc9f1e</code></a>
[ty] Document <code>CallableTypeKind</code> variants (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28277">#28277</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/056ce07a895c217dab7929a616540c246cf8a078"><code>056ce07</code></a>
Add UV_LOCKED to all workflows (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28261">#28261</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/6d6e35d03a72d23d3b0f23fb348692b03c9b81b6"><code>6d6e35d</code></a>
[<code>flake8-pytest-style</code>] Avoid duplicate <code>PT017</code>
diagnostics (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27918">#27918</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/80b4891b64cb916dce080e8fa15af5c2179d776a"><code>80b4891</code></a>
[ty] Temporarily disable tuple types in property tests (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28275">#28275</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/3f7a54d1d7b9480a18d86a8211bf676354a2f8a0"><code>3f7a54d</code></a>
[<code>flake8-tidy-imports</code>] Prevent fix loop between
<code>TID254</code> and <code>TID255</code> (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28262">#28262</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/35656cf37d34f30116d4f4a9cb13853a6ad9fd0d"><code>35656cf</code></a>
[ty] Preserve deprecations on decorated callables (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28256">#28256</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/d2626c866ee08495cfa0da5236e130030d37e0cf"><code>d2626c8</code></a>
[ty] Reject missing attributes on <code>type[]</code> aliases (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28267">#28267</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/ee6a6d19aa0707df942febe9768488d39f383c8c"><code>ee6a6d1</code></a>
[ty] Support ParamSpecs in <code>ConstraintSet</code> (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28028">#28028</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/7f6a170080dab08f754ef086369a2623c70f753d"><code>7f6a170</code></a>
[ty] Move bound APIs to constraint owners (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28094">#28094</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ruff/compare/0.16.5...0.16.6">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-11 00:20:11 -04:00
Nicholas Velten 46d21cafbc Deep-link Search By mode via URL hash (#1311)
Closes #1228.

Search By mode (General/Author/Title/Series/Manual), content type and
the search query now live in the URL as a hash fragment, updated live as
you search, not just parsed once on load. A shared/bookmarked link like
`#search_by=manual&q=foundation` reopens in that exact mode with the
query filled in.

Following the direction from the issue thread:
- Hash fragment instead of query string, so it stays browser side only.
- Live updates via `history.replaceState`, no history spam per
keystroke.
- Default Search By persisted in a client side cookie as fallback when
there's no hash, no user accounts needed.

Tested manually against a local build (search-by switching, hash live
update, deep link reload, cookie fallback when there's no hash, and hash
overriding a stale cookie).
2026-09-05 01:13:59 -04:00
dependabot[bot] 22aa59e567 build(deps): bump the docker-base-image-digests group with 2 updates (#1306)
> [!WARNING]
> Cooldown could not be applied because no publication date was
available from the registry.
>

Bumps the docker-base-image-digests group with 2 updates: node and
[astral-sh/uv](https://github.com/astral-sh/uv).

Updates `node` from `d32cdf6` to `e67514e`

Updates `astral-sh/uv` from 0.12.5 to 0.12.9
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/uv/releases">astral-sh/uv's
releases</a>.</em></p>
<blockquote>
<h2>0.12.9</h2>
<h2>Release Notes</h2>
<p>Released on 2026-09-01.</p>
<h3>Python</h3>
<ul>
<li>Add CPython 3.15.0rc2 (<a
href="https://redirect.github.com/astral-sh/uv/pull/21413">#21413</a>,
<a
href="https://redirect.github.com/astral-sh/uv/pull/21415">#21415</a>)</li>
</ul>
<h3>Enhancements</h3>
<ul>
<li>Add <code>--no-locked</code> and <code>--no-frozen</code> to disable
lock modes enabled by <code>UV_LOCKED</code> and <code>UV_FROZEN</code>
for a single invocation (<a
href="https://redirect.github.com/astral-sh/uv/pull/21408">#21408</a>)</li>
<li>Report the exact command-line lock-mode flag in warnings and errors
(<a
href="https://redirect.github.com/astral-sh/uv/pull/21402">#21402</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Speed up cold wheel installs by extracting each streaming ZIP
archive in a single blocking task and reusing buffers across files (<a
href="https://redirect.github.com/astral-sh/uv/pull/21372">#21372</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Update <code>async_http_range_reader</code> to 0.11.1 to address a
potential memory-safety issue when reading metadata ranges from
untrusted wheels (<a
href="https://redirect.github.com/astral-sh/uv/pull/21401">#21401</a>)</li>
<li>Remove sensitive headers when redirects cross authentication realms,
including same-host redirects that change URL schemes (<a
href="https://redirect.github.com/astral-sh/uv/pull/21382">#21382</a>)</li>
<li>Redact secrets in signed URLs from retry diagnostics, including
nested request errors (<a
href="https://redirect.github.com/astral-sh/uv/pull/21381">#21381</a>)</li>
<li>Give <code>--locked</code>, <code>--frozen</code>,
<code>--check</code>, and <code>--check-exists</code> precedence over
conflicting <code>UV_LOCKED</code> and <code>UV_FROZEN</code> values (<a
href="https://redirect.github.com/astral-sh/uv/pull/21396">#21396</a>)</li>
<li>Prevent concurrent uv processes from redundantly extracting the same
local or source-built wheel (<a
href="https://redirect.github.com/astral-sh/uv/pull/21400">#21400</a>)</li>
</ul>
<h2>Install uv 0.12.9</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://releases.astral.sh/github/uv/releases/download/0.12.9/uv-installer.sh
| sh
</code></pre>
<h3>Install prebuilt binaries via powershell script</h3>
<pre lang="sh"><code>powershell -ExecutionPolicy Bypass -c &quot;irm
https://releases.astral.sh/github/uv/releases/download/0.12.9/uv-installer.ps1
| iex&quot;
</code></pre>
<h2>Download uv 0.12.9</h2>
<table>
<thead>
<tr>
<th>File</th>
<th>Platform</th>
<th>Checksum</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.9/uv-aarch64-apple-darwin.tar.gz">uv-aarch64-apple-darwin.tar.gz</a></td>
<td>Apple Silicon macOS</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.9/uv-aarch64-apple-darwin.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.9/uv-x86_64-apple-darwin.tar.gz">uv-x86_64-apple-darwin.tar.gz</a></td>
<td>Intel macOS</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.9/uv-x86_64-apple-darwin.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.9/uv-aarch64-pc-windows-msvc.zip">uv-aarch64-pc-windows-msvc.zip</a></td>
<td>ARM64 Windows</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.9/uv-aarch64-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.9/uv-i686-pc-windows-msvc.zip">uv-i686-pc-windows-msvc.zip</a></td>
<td>x86 Windows</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.9/uv-i686-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.9/uv-x86_64-pc-windows-msvc.zip">uv-x86_64-pc-windows-msvc.zip</a></td>
<td>x64 Windows</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.9/uv-x86_64-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.9/uv-aarch64-unknown-linux-gnu.tar.gz">uv-aarch64-unknown-linux-gnu.tar.gz</a></td>
<td>ARM64 Linux</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.9/uv-aarch64-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
</tbody>
</table>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/uv/blob/main/CHANGELOG.md">astral-sh/uv's
changelog</a>.</em></p>
<blockquote>
<h2>0.12.9</h2>
<p>Released on 2026-09-01.</p>
<h3>Python</h3>
<ul>
<li>Add CPython 3.15.0rc2 (<a
href="https://redirect.github.com/astral-sh/uv/pull/21413">#21413</a>,
<a
href="https://redirect.github.com/astral-sh/uv/pull/21415">#21415</a>)</li>
</ul>
<h3>Enhancements</h3>
<ul>
<li>Add <code>--no-locked</code> and <code>--no-frozen</code> to disable
lock modes enabled by <code>UV_LOCKED</code> and <code>UV_FROZEN</code>
for a single invocation (<a
href="https://redirect.github.com/astral-sh/uv/pull/21408">#21408</a>)</li>
<li>Report the exact command-line lock-mode flag in warnings and errors
(<a
href="https://redirect.github.com/astral-sh/uv/pull/21402">#21402</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Speed up cold wheel installs by extracting each streaming ZIP
archive in a single blocking task and reusing buffers across files (<a
href="https://redirect.github.com/astral-sh/uv/pull/21372">#21372</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Update <code>async_http_range_reader</code> to 0.11.1 to address a
potential memory-safety issue when reading metadata ranges from
untrusted wheels (<a
href="https://redirect.github.com/astral-sh/uv/pull/21401">#21401</a>)</li>
<li>Remove sensitive headers when redirects cross authentication realms,
including same-host redirects that change URL schemes (<a
href="https://redirect.github.com/astral-sh/uv/pull/21382">#21382</a>)</li>
<li>Redact secrets in signed URLs from retry diagnostics, including
nested request errors (<a
href="https://redirect.github.com/astral-sh/uv/pull/21381">#21381</a>)</li>
<li>Give <code>--locked</code>, <code>--frozen</code>,
<code>--check</code>, and <code>--check-exists</code> precedence over
conflicting <code>UV_LOCKED</code> and <code>UV_FROZEN</code> values (<a
href="https://redirect.github.com/astral-sh/uv/pull/21396">#21396</a>)</li>
<li>Prevent concurrent uv processes from redundantly extracting the same
local or source-built wheel (<a
href="https://redirect.github.com/astral-sh/uv/pull/21400">#21400</a>)</li>
</ul>
<h2>0.12.8</h2>
<p>Released on 2026-08-31.</p>
<h3>Enhancements</h3>
<ul>
<li>Warn about invalid tool directories and continue upgrading valid
tools with <code>uv tool upgrade --all</code> (<a
href="https://redirect.github.com/astral-sh/uv/pull/21368">#21368</a>)</li>
</ul>
<h3>Preview features</h3>
<ul>
<li>Deduplicate identical files within and across cached wheels with the
<code>content-addressed-cache</code> preview feature (<a
href="https://redirect.github.com/astral-sh/uv/pull/21327">#21327</a>)</li>
<li>Reduce allocations while extracting content-addressed wheels by
reusing the hashing buffer across files (<a
href="https://redirect.github.com/astral-sh/uv/pull/21340">#21340</a>)</li>
<li>Speed up content-addressed cache cleanup on macOS by reading
hard-link counts in bulk (<a
href="https://redirect.github.com/astral-sh/uv/pull/21344">#21344</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Prevent concurrent uv processes from downloading and extracting the
same remote wheel more than once (<a
href="https://redirect.github.com/astral-sh/uv/pull/21379">#21379</a>)</li>
<li>Speed up dependency graph construction from large lockfiles by
indexing packages during traversal (<a
href="https://redirect.github.com/astral-sh/uv/pull/21373">#21373</a>)</li>
<li>Extend indexed lockfile traversal to exports, dependency trees,
audits, and freshness checks (<a
href="https://redirect.github.com/astral-sh/uv/pull/21377">#21377</a>)</li>
<li>Speed up warm resolutions by reducing repeated marker interner work
(<a
href="https://redirect.github.com/astral-sh/uv/pull/21300">#21300</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Do not trust hashes from direct URLs discovered only in wheel
metadata when installing with <code>--require-hashes</code> (<a
href="https://redirect.github.com/astral-sh/uv/pull/21348">#21348</a>)</li>
<li>Use a compatible Azure Storage API version for anonymous and
authenticated requests, allowing credential retries when public access
is disabled (<a
href="https://redirect.github.com/astral-sh/uv/pull/21366">#21366</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/uv/commit/9f928602938ac5cf1cd6b294a725833c16f5720e"><code>9f92860</code></a>
Bump version to 0.12.9 (<a
href="https://redirect.github.com/astral-sh/uv/issues/21422">#21422</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/29c546228ed6ba8970bb063960c9a767fae19236"><code>29c5462</code></a>
Skip invalid scripts in <code>uv workspace list --scripts</code> (<a
href="https://redirect.github.com/astral-sh/uv/issues/21416">#21416</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/bf1d7e8049017a363565185a7df7e86f33041452"><code>bf1d7e8</code></a>
Encapsulate wheel extraction directory ownership (<a
href="https://redirect.github.com/astral-sh/uv/issues/21419">#21419</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/6dacee042f58cea9184267ffb70c6960582bb210"><code>6dacee0</code></a>
Run streaming ZIP extraction in a blocking task (<a
href="https://redirect.github.com/astral-sh/uv/issues/21372">#21372</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/91d1ebf1b4ea1fc00928b412a9da23639aacb086"><code>91d1ebf</code></a>
Sync latest Python releases (<a
href="https://redirect.github.com/astral-sh/uv/issues/21415">#21415</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/6a61ddb2c094ab44e55a2cc5bb4a88b7c08d733a"><code>6a61ddb</code></a>
Revert &quot;Limit CI planner checkout depth&quot; (<a
href="https://redirect.github.com/astral-sh/uv/issues/21414">#21414</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/e9d1816b538468e7deb8f267f28775007706b866"><code>e9d1816</code></a>
Add CPython 3.15.0rc2 (<a
href="https://redirect.github.com/astral-sh/uv/issues/21413">#21413</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/e888329afc22fa3e34f93d5ae5e23fbc87328952"><code>e888329</code></a>
Remove unreachable lock configuration sources (<a
href="https://redirect.github.com/astral-sh/uv/issues/21409">#21409</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/bf041f9245952ba7c9e3c29aebe0a8acc2bc983d"><code>bf041f9</code></a>
Allow negating locked and frozen modes (<a
href="https://redirect.github.com/astral-sh/uv/issues/21408">#21408</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/feffc0b5bbf8aeca1dff4a6f042a58e44a5daf09"><code>feffc0b</code></a>
Bump async_http_range_reader to 0.11.1 (<a
href="https://redirect.github.com/astral-sh/uv/issues/21401">#21401</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/uv/compare/0.12.5...0.12.9">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-04 11:03:26 -04:00
dependabot[bot] b317dd1110 build(deps): bump python from cae66f2 to cad9a2c (#1307)
> [!WARNING]
> Cooldown could not be applied because no publication date was
available from the registry.
>

Bumps python from `cae66f2` to `cad9a2c`.


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=python&package-manager=docker&previous-version=3.14.7-slim&new-version=3.14.7-slim)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-04 11:03:15 -04:00
dependabot[bot] 223d8a2256 build(deps): bump the npm-deps group in /src/frontend with 4 updates (#1308)
Bumps the npm-deps group in /src/frontend with 4 updates:
[react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom),
[@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node),
[@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react)
and [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip).

Updates `react-router-dom` from 7.18.2 to 7.18.3
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/remix-run/react-router/blob/react-router-dom@7.18.3/packages/react-router-dom/CHANGELOG.md">react-router-dom's
changelog</a>.</em></p>
<blockquote>
<h2>v7.18.3</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies:
<ul>
<li><a
href="https://github.com/remix-run/react-router/releases/tag/react-router@7.18.3"><code>react-router@7.18.3</code></a></li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/remix-run/react-router/commit/23166dfe7f61323f0d2775af67d2691f9ed0843d"><code>23166df</code></a>
Release v7.18.3 (<a
href="https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom/issues/15424">#15424</a>)</li>
<li>See full diff in <a
href="https://github.com/remix-run/react-router/commits/react-router-dom@7.18.3/packages/react-router-dom">compare
view</a></li>
</ul>
</details>
<br />

Updates `@types/node` from 26.3.0 to 26.4.0
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node">compare
view</a></li>
</ul>
</details>
<br />

Updates `@vitejs/plugin-react` from 6.1.0 to 6.1.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite-plugin-react/releases">@​vitejs/plugin-react's
releases</a>.</em></p>
<blockquote>
<h2>plugin-react@6.1.1</h2>
<h3>Add <code>compiler.logDiagnostics</code> option</h3>
<p>Recoverable React Compiler diagnostics are no longer logged by
default. Set <code>compiler.logDiagnostics</code> to <code>true</code>
to log them through Vite. Fatal diagnostics are always logged and fail
the transform.</p>
<h3>Respect environment sourcemap option for React Compiler transform
when <code>builder.sharedPlugins</code> is enabled (<a
href="https://redirect.github.com/vitejs/vite-plugin-react/pull/1439">#1439</a>)</h3>
<p>The React Compiler transform was using the top-level sourcemap option
instead of the environment sourcemap option. This caused a problem when
the experimental <code>builder.sharedPlugins</code> was enabled.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md">@​vitejs/plugin-react's
changelog</a>.</em></p>
<blockquote>
<h2>6.1.1 (2026-08-28)</h2>
<h3>Add <code>compiler.logDiagnostics</code> option</h3>
<p>Recoverable React Compiler diagnostics are no longer logged by
default. Set <code>compiler.logDiagnostics</code> to <code>true</code>
to log them through Vite. Fatal diagnostics are always logged and fail
the transform.</p>
<h3>Respect environment sourcemap option for React Compiler transform
when <code>builder.sharedPlugins</code> is enabled (<a
href="https://redirect.github.com/vitejs/vite-plugin-react/pull/1439">#1439</a>)</h3>
<p>The React Compiler transform was using the top-level sourcemap option
instead of the environment sourcemap option. This caused a problem when
the experimental <code>builder.sharedPlugins</code> was enabled.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/04cac5020e349f452d76c5a4f6d788ad4b38930a"><code>04cac50</code></a>
release: plugin-react@6.1.1 (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1440">#1440</a>)</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/82d35abe4946eddd4e6456802bf2b53444e264f2"><code>82d35ab</code></a>
fix(react): respect environment sourcemap option when
<code>builder.sharedPlugins</code>...</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/397e8471a559f18a16dd21bd797ac01a369dabdc"><code>397e847</code></a>
fix(react): make logging diagnostics an opt-in for React Compiler (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1431">#1431</a>)</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/61006e6f52124821c24121a78712f7162ae36f5b"><code>61006e6</code></a>
fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1433">#1433</a>)</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/e2a649cbaa7334d6991f843563683975667e1be1"><code>e2a649c</code></a>
chore: use <code>deps.neverBundle</code> instead of
<code>external</code> in tsdown config (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1430">#1430</a>)</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/fb2d6f3635acbb0f3acbd0e9a914f6c620460957"><code>fb2d6f3</code></a>
fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1427">#1427</a>)</li>
<li>See full diff in <a
href="https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.1.1/packages/plugin-react">compare
view</a></li>
</ul>
</details>
<br />

Updates `knip` from 6.32.2 to 6.34.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/webpro-nl/knip/releases">knip's
releases</a>.</em></p>
<blockquote>
<h2>Release 6.34.0</h2>
<ul>
<li>fix: report types used only in private parameters (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1950">#1950</a>)
(3c785f55ce608d7921e37e41dd5fc61b623e5da2)</li>
<li>💡 Add support for <code>node:fs/promises</code> <code>glob</code>
(<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1977">#1977</a>)
(44faf08e7f56ce695d1541a6c337b76bea0328d1) - thanks <a
href="https://github.com/DreamLongYT"><code>@​DreamLongYT</code></a>!</li>
<li>Respect JSDoc tags on URL dependencies (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1943">#1943</a>)
(f93ad0997e1b8fc644f6487df52803cba6f7a9ad)</li>
<li>Make prop access consistent while at it
(1c90b49b1b196795df6dcf9d7e85bc1b1399667a)</li>
<li>Set an explicit Bun test timeout
(964085dfab0165db2658e51cb787e2301886f9ce)</li>
<li>Add note re. one scoped PRs
(8f9490a581874e0fd7246e60223b20938bf4ebb1)</li>
<li>fix(cli): validate numeric options instead of passing NaN through
(<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1975">#1975</a>)
(ab90e2428a16ff50fe5323ce73172f6f1efb9730) - thanks <a
href="https://github.com/devYRPauli"><code>@​devYRPauli</code></a>!</li>
<li>fix: compile files with workspace-scoped compilers (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1997">#1997</a>)
(462ae45df33a5ee2e2185f3b2db756b13dfbadd3) - thanks <a
href="https://github.com/gioboa"><code>@​gioboa</code></a>!</li>
<li>Resolve Nuxt <code>#shared</code> imports from the shared directory
(<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1991">#1991</a>)
(cc470611042b01761491c21f6987549db4c11df8) - thanks <a
href="https://github.com/DreamLongYT"><code>@​DreamLongYT</code></a>!</li>
<li>Update dependencies (c4e0f082fbd7fb508a411c7dd0a782ea4556527c)</li>
<li>Declare webpack directly instead of through
<code>@types/webpack</code>
(96ff2df58dd1d229d077212e1cd80f10889650ca)</li>
<li>Assert counters in targeted regression tests
(f0cf11dfcfbdeac053bba0dd12212356a67f6464)</li>
</ul>
<h2>Release 6.33.0</h2>
<ul>
<li>Support package manifest resolution under Yarn PnP (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1946">#1946</a>)
(a82efb62b290e004bdabee40bcb337baf3701a1b) - thanks <a
href="https://github.com/WooWan"><code>@​WooWan</code></a>!</li>
<li>Resolve oxlint jsPlugins from .oxlintrc.jsonc (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1973">#1973</a>)
(d055023182789555a574eaefde80dac997190437) - thanks <a
href="https://github.com/DreierF"><code>@​DreierF</code></a>!</li>
<li>Update dependency bun to v1.4.0 (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1964">#1964</a>)
(937ab38ad514a00edafecff4ca04603b015bf921) - thanks <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot]!</li>
<li>Expose <code>defineConfig</code> on a <code>./config</code>
entrypoint (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1960">#1960</a>)
(17f7a6051d9781860f248cc2a745dbe837713cd9) - thanks <a
href="https://github.com/theRizwan"><code>@​theRizwan</code></a>!</li>
<li>Preserve all environment variables in exec.ts (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1979">#1979</a>)
(c0a9ecf8d1c0cdd7789c1e1bea893706c060239d) - thanks <a
href="https://github.com/DreamLongYT"><code>@​DreamLongYT</code></a>!</li>
<li>Add support for TypeScript content mappers (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1978">#1978</a>)
(db67f9bd8cebf0ea3c298690d462c154827aa462) - thanks <a
href="https://github.com/remcohaszing"><code>@​remcohaszing</code></a>!</li>
<li>fix(vitest): resolve array entries and root-relative setup
dependencies (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1982">#1982</a>)
(0ecf48538c339437e6f9dfe9a6013d307783cbd3) - thanks <a
href="https://github.com/DreamLongYT"><code>@​DreamLongYT</code></a>!</li>
<li>Fix type annotation in dynamic configuration function example
(ada88c7f01563b73643af34a147a71fd9ab9d2dc)</li>
<li>Format docs with remark
(7764c2a4ebecdbf226c19494d255d9cb14550822)</li>
<li>Throw on configuration that is not an object
(d7ac0cf1de8f79be6ec53412cac9f7edf8f35ba7)</li>
<li>Clarify dependency inputs, isTypeOnly and toConfig in plugin docs
(2bd367961554ad1addb520bdfad5e31125885969)</li>
<li>Resolve content mapper process from
package.json#typescript.contentMapper.exec
(74640c03e450624409692f4cc17699775108fd7e)</li>
<li>Encode argv arrays before parsing them as shell commands
(b79234c15153fbf592e82c94e4e4e9f41b4479e0)</li>
<li>Update dependencies (28510146c2b97d88f7188f1239b9307aac7dbf0c)</li>
<li>Replace non-breaking space in module graph comment
(4e22bfcd09deccc92ed158f5554fe3e03f3a2c51)</li>
<li>Update fix format test for oxfmt package.json key sorting
(65011bf6054d03fb5580f3381867b07d1b8a9a0c)</li>
<li>fix(webpack): resolve entry descriptors using import field (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1981">#1981</a>)
(cad8910e7fb89c2510fc9f5885289335dd4e60da) - thanks <a
href="https://github.com/DreamLongYT"><code>@​DreamLongYT</code></a>!</li>
<li>fix: count enum element access as a whole-object read (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1958">#1958</a>)
(5a47d603aa23b388ae7ce4fe301656f1abc43b14) - thanks <a
href="https://github.com/addielaruee"><code>@​addielaruee</code></a>!</li>
<li>Resolve oxlint/vite-plus import resolvers from <code>settings</code>
(<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1983">#1983</a>)
(c1165b70efe27398b86fa50bcea7d85c253d3ee7) - thanks <a
href="https://github.com/DreierF"><code>@​DreierF</code></a>!</li>
<li>fix(nuxt): resolve string extends configs (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1984">#1984</a>)
(4a5d7068d6264ea45c78c832abe7ba052763517e) - thanks <a
href="https://github.com/DreamLongYT"><code>@​DreamLongYT</code></a>!</li>
<li>Wrap up webpack entry resolution
(505decdb821f9b76d194742cee7e98c75ef7d8e9)</li>
<li>Keep element access precise for number-like enum member names
(bcbac3a8b8283da3b32256584b3cc6569ae5c02f)</li>
<li>Resolve <code>Object.*</code> enum enumeration through import
aliases (347967daaed817ca20e1ad6148388a66ec8b421b)</li>
<li>Resolve named string-key access on namespace-qualified enums
(2ac1fbc1e92f3dae662ca4d4701cf010346be49a)</li>
<li>Normalize line endings in nuxt-config-string fixture
(9fe2b2b2db8690685ec7c6cf8b3d39b9ed7b3fdc)</li>
<li>Resolve nuxt layer sources from all <code>extends</code> entry
shapes (db136151d9f98801837b33646ef7b02ae7b5a86d)</li>
<li>Report unused exports behind Vitest module promise mocks (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1966">#1966</a>)
(74954c219367ca1577184cca08c30dbb3fb6222e)</li>
<li>Check line endings against <code>.gitattributes</code> in <code>pnpm
run ci</code> (ddc9fd0cc3328260665203c8573702bb6c9d0dd6)</li>
</ul>
<h2>Release 6.32.3</h2>
<ul>
<li>Update sentry production snapshot
(9f18cba93c91de8554636b13692d6e7a937b7697)</li>
<li>Pin TypeScript ecosystem test
(de5162952d223050f3135afb02d4e8de6d46f381)</li>
<li>Update query snapshot
(f6964dd577d8f7967f9be6778f5c3953632e3c96)</li>
<li>fix(tsup): match config files with .cts and .mts extensions (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1938">#1938</a>)
(68d6f52f7913cfa1e36618e6b03f90734da34149) - thanks <a
href="https://github.com/codeAnqiang-ma"><code>@​codeAnqiang-ma</code></a>!</li>
<li>fix(lefthook): match all config file names and extensions (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1940">#1940</a>)
(4015ae0330c78e97360a2c8f697d17f82c5c24ae) - thanks <a
href="https://github.com/codeAnqiang-ma"><code>@​codeAnqiang-ma</code></a>!</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/webpro-nl/knip/commit/4ffebb746c630147c5d94c14da0bafb60af3c027"><code>4ffebb7</code></a>
Release knip@6.34.0</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/f0cf11dfcfbdeac053bba0dd12212356a67f6464"><code>f0cf11d</code></a>
Assert counters in targeted regression tests</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/96ff2df58dd1d229d077212e1cd80f10889650ca"><code>96ff2df</code></a>
Declare webpack directly instead of through
<code>@types/webpack</code></li>
<li><a
href="https://github.com/webpro-nl/knip/commit/c4e0f082fbd7fb508a411c7dd0a782ea4556527c"><code>c4e0f08</code></a>
Update dependencies</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/cc470611042b01761491c21f6987549db4c11df8"><code>cc47061</code></a>
Resolve Nuxt <code>#shared</code> imports from the shared directory (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1991">#1991</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/462ae45df33a5ee2e2185f3b2db756b13dfbadd3"><code>462ae45</code></a>
fix: compile files with workspace-scoped compilers (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1997">#1997</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/ab90e2428a16ff50fe5323ce73172f6f1efb9730"><code>ab90e24</code></a>
fix(cli): validate numeric options instead of passing NaN through (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1975">#1975</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/964085dfab0165db2658e51cb787e2301886f9ce"><code>964085d</code></a>
Set an explicit Bun test timeout</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/1c90b49b1b196795df6dcf9d7e85bc1b1399667a"><code>1c90b49</code></a>
Make prop access consistent while at it</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/f93ad0997e1b8fc644f6487df52803cba6f7a9ad"><code>f93ad09</code></a>
Respect JSDoc tags on URL dependencies (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1943">#1943</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/webpro-nl/knip/commits/knip@6.34.0/packages/knip">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-04 11:02:22 -04:00
dependabot[bot] 7b5853f35d build(deps): bump the gh-actions group with 3 updates (#1310)
Bumps the gh-actions group with 3 updates:
[github/codeql-action/init](https://github.com/github/codeql-action),
[github/codeql-action/autobuild](https://github.com/github/codeql-action)
and
[github/codeql-action/analyze](https://github.com/github/codeql-action).

Updates `github/codeql-action/init` from 4.37.8 to 4.37.9
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/init's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.9</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.4">2.26.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4106">#4106</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/init's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<ul>
<li>The CodeQL Action now supports CodeQL releases that are compatible
with Linux Arm64 and download the native <code>linux-arm64</code> CodeQL
bundle when available. <a
href="https://redirect.github.com/github/codeql-action/pull/4072">#4072</a></li>
</ul>
<h2>4.37.9 - 26 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.4">2.26.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4106">#4106</a></li>
</ul>
<h2>4.37.8 - 21 Aug 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.7 - 13 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/cdf488f595d80d6e07e03d4674febd5ab45fa938"><code>cdf488f</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4107">#4107</a>
from github/update-v4.37.9-920ba7cd1</li>
<li><a
href="https://github.com/github/codeql-action/commit/7243f38558d187dde99730d224bb47aa26a95306"><code>7243f38</code></a>
Update changelog for v4.37.9</li>
<li><a
href="https://github.com/github/codeql-action/commit/920ba7cd1596037e042122c00381eb16b397d68e"><code>920ba7c</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4106">#4106</a>
from github/update-bundle/codeql-bundle-v2.26.4</li>
<li><a
href="https://github.com/github/codeql-action/commit/ecfa6e16817b8f490bc9a59baa391baf4fa3e3c2"><code>ecfa6e1</code></a>
Add changelog note</li>
<li><a
href="https://github.com/github/codeql-action/commit/adcdf4a70d247343cf9c29e0f7a6658b51c3a2b1"><code>adcdf4a</code></a>
Update default bundle to codeql-bundle-v2.26.4</li>
<li><a
href="https://github.com/github/codeql-action/commit/486fec2a3ea2626afcd8c7e9208b4f515078dd7e"><code>486fec2</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4099">#4099</a>
from github/update-supported-enterprise-server-versions</li>
<li><a
href="https://github.com/github/codeql-action/commit/134624c67b20869c2aaa36dafa726375b78a5d76"><code>134624c</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4101">#4101</a>
from github/dependabot/npm_and_yarn/npm-minor-457d82...</li>
<li><a
href="https://github.com/github/codeql-action/commit/ff43db8f982a368288f117354fb8d046e937124c"><code>ff43db8</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4103">#4103</a>
from github/mergeback/v4.37.8-to-main-db488dde</li>
<li><a
href="https://github.com/github/codeql-action/commit/4605e03a74cf891614c4d76f82384a16c1c11816"><code>4605e03</code></a>
Rebuild</li>
<li><a
href="https://github.com/github/codeql-action/commit/099c869cad6bf3b88657154d4ae47ffed27e632d"><code>099c869</code></a>
Update changelog and version after v4.37.8</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28...cdf488f595d80d6e07e03d4674febd5ab45fa938">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/autobuild` from 4.37.8 to 4.37.9
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/autobuild's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.9</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.4">2.26.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4106">#4106</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/autobuild's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<ul>
<li>The CodeQL Action now supports CodeQL releases that are compatible
with Linux Arm64 and download the native <code>linux-arm64</code> CodeQL
bundle when available. <a
href="https://redirect.github.com/github/codeql-action/pull/4072">#4072</a></li>
</ul>
<h2>4.37.9 - 26 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.4">2.26.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4106">#4106</a></li>
</ul>
<h2>4.37.8 - 21 Aug 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.7 - 13 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/cdf488f595d80d6e07e03d4674febd5ab45fa938"><code>cdf488f</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4107">#4107</a>
from github/update-v4.37.9-920ba7cd1</li>
<li><a
href="https://github.com/github/codeql-action/commit/7243f38558d187dde99730d224bb47aa26a95306"><code>7243f38</code></a>
Update changelog for v4.37.9</li>
<li><a
href="https://github.com/github/codeql-action/commit/920ba7cd1596037e042122c00381eb16b397d68e"><code>920ba7c</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4106">#4106</a>
from github/update-bundle/codeql-bundle-v2.26.4</li>
<li><a
href="https://github.com/github/codeql-action/commit/ecfa6e16817b8f490bc9a59baa391baf4fa3e3c2"><code>ecfa6e1</code></a>
Add changelog note</li>
<li><a
href="https://github.com/github/codeql-action/commit/adcdf4a70d247343cf9c29e0f7a6658b51c3a2b1"><code>adcdf4a</code></a>
Update default bundle to codeql-bundle-v2.26.4</li>
<li><a
href="https://github.com/github/codeql-action/commit/486fec2a3ea2626afcd8c7e9208b4f515078dd7e"><code>486fec2</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4099">#4099</a>
from github/update-supported-enterprise-server-versions</li>
<li><a
href="https://github.com/github/codeql-action/commit/134624c67b20869c2aaa36dafa726375b78a5d76"><code>134624c</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4101">#4101</a>
from github/dependabot/npm_and_yarn/npm-minor-457d82...</li>
<li><a
href="https://github.com/github/codeql-action/commit/ff43db8f982a368288f117354fb8d046e937124c"><code>ff43db8</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4103">#4103</a>
from github/mergeback/v4.37.8-to-main-db488dde</li>
<li><a
href="https://github.com/github/codeql-action/commit/4605e03a74cf891614c4d76f82384a16c1c11816"><code>4605e03</code></a>
Rebuild</li>
<li><a
href="https://github.com/github/codeql-action/commit/099c869cad6bf3b88657154d4ae47ffed27e632d"><code>099c869</code></a>
Update changelog and version after v4.37.8</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28...cdf488f595d80d6e07e03d4674febd5ab45fa938">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/analyze` from 4.37.8 to 4.37.9
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/analyze's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.9</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.4">2.26.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4106">#4106</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/analyze's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<ul>
<li>The CodeQL Action now supports CodeQL releases that are compatible
with Linux Arm64 and download the native <code>linux-arm64</code> CodeQL
bundle when available. <a
href="https://redirect.github.com/github/codeql-action/pull/4072">#4072</a></li>
</ul>
<h2>4.37.9 - 26 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.4">2.26.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4106">#4106</a></li>
</ul>
<h2>4.37.8 - 21 Aug 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.7 - 13 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/cdf488f595d80d6e07e03d4674febd5ab45fa938"><code>cdf488f</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4107">#4107</a>
from github/update-v4.37.9-920ba7cd1</li>
<li><a
href="https://github.com/github/codeql-action/commit/7243f38558d187dde99730d224bb47aa26a95306"><code>7243f38</code></a>
Update changelog for v4.37.9</li>
<li><a
href="https://github.com/github/codeql-action/commit/920ba7cd1596037e042122c00381eb16b397d68e"><code>920ba7c</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4106">#4106</a>
from github/update-bundle/codeql-bundle-v2.26.4</li>
<li><a
href="https://github.com/github/codeql-action/commit/ecfa6e16817b8f490bc9a59baa391baf4fa3e3c2"><code>ecfa6e1</code></a>
Add changelog note</li>
<li><a
href="https://github.com/github/codeql-action/commit/adcdf4a70d247343cf9c29e0f7a6658b51c3a2b1"><code>adcdf4a</code></a>
Update default bundle to codeql-bundle-v2.26.4</li>
<li><a
href="https://github.com/github/codeql-action/commit/486fec2a3ea2626afcd8c7e9208b4f515078dd7e"><code>486fec2</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4099">#4099</a>
from github/update-supported-enterprise-server-versions</li>
<li><a
href="https://github.com/github/codeql-action/commit/134624c67b20869c2aaa36dafa726375b78a5d76"><code>134624c</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4101">#4101</a>
from github/dependabot/npm_and_yarn/npm-minor-457d82...</li>
<li><a
href="https://github.com/github/codeql-action/commit/ff43db8f982a368288f117354fb8d046e937124c"><code>ff43db8</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4103">#4103</a>
from github/mergeback/v4.37.8-to-main-db488dde</li>
<li><a
href="https://github.com/github/codeql-action/commit/4605e03a74cf891614c4d76f82384a16c1c11816"><code>4605e03</code></a>
Rebuild</li>
<li><a
href="https://github.com/github/codeql-action/commit/099c869cad6bf3b88657154d4ae47ffed27e632d"><code>099c869</code></a>
Update changelog and version after v4.37.8</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28...cdf488f595d80d6e07e03d4674febd5ab45fa938">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-04 11:02:15 -04:00
Ama Senevirathneandamasen02 c18da92569 fix(postprocess): attach unmatched chaptered audio files to existing book group (#1176) (#1309)
### Summary
Fixes #1176

When downloading an audiobook with many chaptered tracks (e.g. 250+
`.flac` or `.mp3` files), indexer XML or release metadata often caps the
file list at ~100-110 entries. When the release extracts on disk,
`match_plan_to_files()` matched those first ~110 files to the planned
book group, while the remaining 140+ files fell into `unmatched` and
triggered fallback heuristic grouping.

Because heuristic grouping parsed the folder name (`Westwell - Hot &
Cold (2023)`) and stripped the series/author prefix, it generated a
second book titled `Hot & Cold` containing the remaining tracks,
resulting in two split book folders.

### Changes
- In `match_plan_to_files()`
(`shelfmark/download/postprocess/packs.py`), check `unmatched` files
before falling back to heuristic multi-book splitting.
- If an unmatched file is chaptered audio (`.flac`, `.mp3`, `.aac`,
etc.) and shares the directory with an existing book group, or if the
plan was a single-book plan, append it to that group instead of creating
a secondary book.
- Non-chaptered standalone books (e.g. `.m4b`, `.epub`) or files in
separate subfolders continue to fall back to heuristic grouping as
before.
- Added unit tests in `tests/download/test_packs.py` verifying:
1. Truncated track list in single folder properly appends remaining
chaptered tracks without splitting.
2. Single-book plan with multi-disc audio files (`CD1`/`CD2`) groups
together cleanly.
3. Multi-book packs with unmatched chaptered tracks route each track to
its respective book folder.

### Testing
Ran `uv run pytest tests/download/test_packs.py` and `uv run pytest
tests/core/test_processing_packs.py` (all passed cleanly). Checked type
annotations with `basedpyright` (0 errors) and formatting with `ruff`.

Co-authored-by: amasen02 <amasen02@users.noreply.github.com>
2026-09-04 11:01:55 -04:00
Jorge Lima 97d1bb0df4 fix(bypass): keep Anna's Archive's aa_ddg_check so clearance replays (#1305)
## What

Add `aa_ddg_check` to the cookie-store allowlist. One name, one test.

## Why

Every replay of stored clearance ends in the `?check=1` redirect loop,
so each search pays a fresh browser solve. On this instance (v1.3.15,
WireGuard egress, 0 VPN restarts across the traces) not one replay was
accepted in three days of DEBUG logs.

The `__ddg*` cookies are stored and replayed correctly. Anna's Archive
also sets a cookie of its own, `aa_ddg_check`, and its `?check=1` hop
only answers with the page when that cookie is present too. The
allowlist keeps `cf_*` and `__ddg*` names, so this one was never stored.

## Measured, same egress IP, cookies taken from one solve

| replayed | plain `requests` | `curl_cffi`, Chrome TLS fingerprint |
|---|---|---|
| filtered `__ddg*` only (current behaviour) | 302 → 302 → 302 … loop |
302 → 302 → 302 … loop |
| filtered + `__ddg8_/9_/10_` | loop | loop |
| filtered + `aa_ddg_check` | **302 → 200, real search page** | 302 →
200 |
| `aa_ddg_check` alone | 302 → 403 | — |

So the TLS fingerprint is not the problem, the per-check trio is not the
answer, and the cookie needs the `__ddg*` clearance next to it. Cookie
attributes as issued: domain `.annas-archive.gl`, path `/`, expiry 90
days. It is not bound to the query, and it is accepted with a stock
Python User-Agent.

## Through the real fetch path

Same process, `html_get_page`, the name allowlisted, three different
queries:

```
1st: solve expected         25.8s  bypass_calls=1  title='frankenstein shelley - search - an'  md5=True
2nd: other query             9.5s  bypass_calls=0  title='pride and prejudice austen - searc'  md5=True
3rd: third query             4.8s  bypass_calls=0  title='dracula stoker - search - anna's a'  md5=True
```

## Notes

- `tests/bypass/test_ddg_cookie_reuse.py` gains
`test_aa_check_cookie_is_stored`; its docstring table gains the row. The
bypass tests need seleniumbase to import and do not run on my macOS
host, so this leans on CI. `ruff check` and `ruff format --check` pass.
The logic was checked directly against `cookie_store` with the settings
registry stubbed.
- `__ddgmark_` carries a 24 h expiry, so the store's clearance is good
for about a day before the next solve, which is what a browser would see
too.
- Follow-up to #1286. Same instance, same method: DEBUG trace, then a
probe script inside the container.
2026-09-03 23:20:48 -04:00
Matt Van HornandMatt Van Horn 9f11e83e1f fix: keep polling queued Real-Debrid torrents (#1303)
Add `queued` to the existing set of non-terminal Real-Debrid torrent
states so `_handle_torrent_info` returns an in-progress `DownloadStatus`
and leaves the mutable download state eligible for subsequent polling.
Keep the change within the existing status-classification path rather
than introducing a new helper or changing the broader handling of
unknown statuses. The native Real-Debrid client currently treats the
documented `queued` torrent status as a terminal error because it is
absent from `_STATUS_DOWNLOADING`. This occurs after a torrent has been
added and its files selected, particularly for uncached torrents that
wait before downloading.

A torrent-info payload with `status: queued`, zero progress, and a
filename returns a non-complete `DownloadState.DOWNLOADING` result
rather than `DownloadState.ERROR`; After handling `queued`, the internal
download state remains non-terminal so a later status poll can be
processed instead of returning a cached error.

Fixes #1268

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-09-03 23:19:50 -04:00
dependabot[bot] b98c2cb83e build(deps): bump the python-deps group with 2 updates (#1302)
Bumps the python-deps group with 2 updates:
[authlib](https://github.com/authlib/authlib) and
[apprise](https://github.com/caronc/apprise).

Updates `authlib` from 1.7.2 to 1.8.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/authlib/authlib/releases">authlib's
releases</a>.</em></p>
<blockquote>
<h2>v1.8.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Prefer <code>id_token_signed_response_alg</code> client metadata to
guess algs by <a
href="https://github.com/azmeuk"><code>@​azmeuk</code></a> in <a
href="https://redirect.github.com/authlib/authlib/pull/888">authlib/authlib#888</a></li>
<li>fix: Catch InvalidKeyIdError in RFC 9068 JWTBearerTokenValidator by
<a
href="https://github.com/liudonggalaxy"><code>@​liudonggalaxy</code></a>
in <a
href="https://redirect.github.com/authlib/authlib/pull/891">authlib/authlib#891</a></li>
<li>fix: make leeway configurable in JWTBearerTokenValidator by <a
href="https://github.com/mondi04"><code>@​mondi04</code></a> in <a
href="https://redirect.github.com/authlib/authlib/pull/903">authlib/authlib#903</a></li>
<li>feat: add default jti claim to sign_jwt_bearer_assertion by <a
href="https://github.com/liudonggalaxy"><code>@​liudonggalaxy</code></a>
in <a
href="https://redirect.github.com/authlib/authlib/pull/897">authlib/authlib#897</a></li>
<li>fix(oauth): cast sub claim to string in JWTBearerTokenGenerator by
<a href="https://github.com/levinKaus"><code>@​levinKaus</code></a> in
<a
href="https://redirect.github.com/authlib/authlib/pull/911">authlib/authlib#911</a></li>
<li>Declare lower bounds for dependencies by <a
href="https://github.com/azmeuk"><code>@​azmeuk</code></a> in <a
href="https://redirect.github.com/authlib/authlib/pull/912">authlib/authlib#912</a></li>
<li>feat(client): use httpx2 instead of httpx by <a
href="https://github.com/levinKaus"><code>@​levinKaus</code></a> in <a
href="https://redirect.github.com/authlib/authlib/pull/909">authlib/authlib#909</a></li>
<li>Fix RFC7523 malformed claims handling by <a
href="https://github.com/azmeuk"><code>@​azmeuk</code></a> in <a
href="https://redirect.github.com/authlib/authlib/pull/916">authlib/authlib#916</a></li>
<li>Fix httpx oauth1 binary form data by <a
href="https://github.com/shc261392"><code>@​shc261392</code></a> in <a
href="https://redirect.github.com/authlib/authlib/pull/779">authlib/authlib#779</a></li>
<li>fix(starlette_client): remove default= keyword from config.get calls
by <a
href="https://github.com/aliaksei-protchanka"><code>@​aliaksei-protchanka</code></a>
in <a
href="https://redirect.github.com/authlib/authlib/pull/770">authlib/authlib#770</a></li>
<li>Added client_id parameter to AssertionClient by <a
href="https://github.com/vilmar-hillow"><code>@​vilmar-hillow</code></a>
in <a
href="https://redirect.github.com/authlib/authlib/pull/476">authlib/authlib#476</a></li>
<li>fix(oauth): save device credential with authenticated client id by
<a
href="https://github.com/arpitjain099"><code>@​arpitjain099</code></a>
in <a
href="https://redirect.github.com/authlib/authlib/pull/908">authlib/authlib#908</a></li>
<li>fix(oauth1): correct protocol name in InsecureTransportError
description by <a
href="https://github.com/RavSinghChandan"><code>@​RavSinghChandan</code></a>
in <a
href="https://redirect.github.com/authlib/authlib/pull/919">authlib/authlib#919</a></li>
<li>fix(client): client can be flexible with jwt's header by <a
href="https://github.com/lepture"><code>@​lepture</code></a> in <a
href="https://redirect.github.com/authlib/authlib/pull/922">authlib/authlib#922</a></li>
<li>fix(oidc): omit claims when the value is None by <a
href="https://github.com/lepture"><code>@​lepture</code></a> in <a
href="https://redirect.github.com/authlib/authlib/pull/923">authlib/authlib#923</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/mondi04"><code>@​mondi04</code></a> made
their first contribution in <a
href="https://redirect.github.com/authlib/authlib/pull/903">authlib/authlib#903</a></li>
<li><a href="https://github.com/levinKaus"><code>@​levinKaus</code></a>
made their first contribution in <a
href="https://redirect.github.com/authlib/authlib/pull/911">authlib/authlib#911</a></li>
<li><a
href="https://github.com/aliaksei-protchanka"><code>@​aliaksei-protchanka</code></a>
made their first contribution in <a
href="https://redirect.github.com/authlib/authlib/pull/770">authlib/authlib#770</a></li>
<li><a
href="https://github.com/vilmar-hillow"><code>@​vilmar-hillow</code></a>
made their first contribution in <a
href="https://redirect.github.com/authlib/authlib/pull/476">authlib/authlib#476</a></li>
<li><a
href="https://github.com/arpitjain099"><code>@​arpitjain099</code></a>
made their first contribution in <a
href="https://redirect.github.com/authlib/authlib/pull/908">authlib/authlib#908</a></li>
<li><a
href="https://github.com/RavSinghChandan"><code>@​RavSinghChandan</code></a>
made their first contribution in <a
href="https://redirect.github.com/authlib/authlib/pull/919">authlib/authlib#919</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/authlib/authlib/compare/v1.7.2...v1.8.0">https://github.com/authlib/authlib/compare/v1.7.2...v1.8.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/authlib/authlib/commit/1a86748b31a2b1940b09cf627d1b70e03d85c077"><code>1a86748</code></a>
chore: release 1.8.0</li>
<li><a
href="https://github.com/authlib/authlib/commit/e4fb941d4959a11e875b34880c41f46ba7cd8c67"><code>e4fb941</code></a>
Merge pull request <a
href="https://redirect.github.com/authlib/authlib/issues/923">#923</a>
from authlib/fix-921</li>
<li><a
href="https://github.com/authlib/authlib/commit/9f2cd2e4c8c2d88cd8c7831d2c612ad38a4b0958"><code>9f2cd2e</code></a>
Merge pull request <a
href="https://redirect.github.com/authlib/authlib/issues/922">#922</a>
from authlib/fix-902</li>
<li><a
href="https://github.com/authlib/authlib/commit/5d53239e8a0ca7ea1c241e84bfd5fd453203a94a"><code>5d53239</code></a>
fix(oidc): omit claims when the value is None</li>
<li><a
href="https://github.com/authlib/authlib/commit/772bc72fe8440e6c5dd0a6615fb42d7466f85546"><code>772bc72</code></a>
Merge pull request <a
href="https://redirect.github.com/authlib/authlib/issues/919">#919</a>
from RavSinghChandan/fix-oauth1-insecure-transport-de...</li>
<li><a
href="https://github.com/authlib/authlib/commit/11794b00375127bbc4397e7ebd46e047c3935b64"><code>11794b0</code></a>
Merge pull request <a
href="https://redirect.github.com/authlib/authlib/issues/908">#908</a>
from arpitjain099/chore/device-cred-authenticated-cli...</li>
<li><a
href="https://github.com/authlib/authlib/commit/13f2eb0589cec6df53c4e1146b11dc0cdd3012c7"><code>13f2eb0</code></a>
fix(client): Added client_id parameter to AssertionClient (<a
href="https://redirect.github.com/authlib/authlib/issues/476">#476</a>)</li>
<li><a
href="https://github.com/authlib/authlib/commit/dde5b7a8015904aba63dad76d7286aa431249783"><code>dde5b7a</code></a>
fix(client): rollback</li>
<li><a
href="https://github.com/authlib/authlib/commit/e4aec8c5c1e333951f49ba6aa3fe7487c6fce670"><code>e4aec8c</code></a>
fix(client): remove default= keyword from starlette client config.get
calls (...</li>
<li><a
href="https://github.com/authlib/authlib/commit/62f41cc2f55c15e658f7a0d2bdcb23feeeb93410"><code>62f41cc</code></a>
fix(client): httpx oauth1 binary form data (<a
href="https://redirect.github.com/authlib/authlib/issues/779">#779</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/authlib/authlib/compare/v1.7.2...v1.8.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `apprise` from 1.13.0 to 1.13.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/caronc/apprise/releases">apprise's
releases</a>.</em></p>
<blockquote>
<h2>Release v1.13.1</h2>
<h2>What's Changed</h2>
<p>Just a small update to fix a small stability issue with Attachments
under some circumstances and the few PR's made by others in one more
small bundle.</p>
<p><strong>Note:</strong> ⚠️ <strong>This will be the last version of
Apprise v1.x</strong> (this time i mean it 😉 ) . Future releases
will be under Apprise v2.x. Again, I strongly want to urge Developers:
<em>please</em> make sure you pin your <code>projects.toml</code> or
<code>requirements.txt</code> to <code>apprise &gt;=1.0.0,
&lt;2.0.0</code>. v2.x will be awesome, but it WILL introduce breaking
changes your software may need to adapt to. I will still continue
supporting the v1.x branch should security issues arise for at year or
two, but eventually v2.x would be the way to go.</p>
<h3>📣 New Notification Services:</h3>
<ul>
<li>Add Signalgrid notification service plugin by <a
href="https://github.com/signalgridco"><code>@​signalgridco</code></a>
in <a
href="https://redirect.github.com/caronc/apprise/pull/1710">caronc/apprise#1710</a></li>
<li>Add Lauther notification support by <a
href="https://github.com/csanadymiklos"><code>@​csanadymiklos</code></a>
in <a
href="https://redirect.github.com/caronc/apprise/pull/1715">caronc/apprise#1715</a></li>
</ul>
<h3>🐞  Bugfixes</h3>
<ul>
<li>Fixes Gento Linux issue with memory based attachment handling in <a
href="https://redirect.github.com/caronc/apprise/pull/1709">caronc/apprise#1709</a>
<ul>
<li>☝️ This is the core reason for this minor update</li>
</ul>
</li>
<li>Allow periods in Pingram API keys (JWT format) by <a
href="https://github.com/lipusal"><code>@​lipusal</code></a> in <a
href="https://redirect.github.com/caronc/apprise/pull/1714">caronc/apprise#1714</a></li>
<li>fixes intermittent test failures for matrix in <a
href="https://redirect.github.com/caronc/apprise/pull/1717">caronc/apprise#1717</a></li>
</ul>
<h3>💡 Features</h3>
<p>n/a</p>
<h3>❤️ Life-Cycle Support</h3>
<ul>
<li>Allow Py 3.13+, please by <a
href="https://github.com/milos-korenciak"><code>@​milos-korenciak</code></a>
in <a
href="https://redirect.github.com/caronc/apprise/pull/1711">caronc/apprise#1711</a></li>
<li>fix(matrix): typofix in label by <a
href="https://github.com/arthurlutz"><code>@​arthurlutz</code></a> in <a
href="https://redirect.github.com/caronc/apprise/pull/1707">caronc/apprise#1707</a></li>
<li>docs: fix typo recomended -&gt; recommended by <a
href="https://github.com/vaibhav8a"><code>@​vaibhav8a</code></a> in <a
href="https://redirect.github.com/caronc/apprise/pull/1716">caronc/apprise#1716</a></li>
</ul>
<h2>Installation</h2>
<p>Apprise is available <a href="https://pypi.org/project/apprise/">on
PyPI</a> through <em>pip</em>:</p>
<pre lang="bash"><code># Install Apprise v1.13.1 from PyPI
pip install apprise==1.13.1
</code></pre>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/arthurlutz"><code>@​arthurlutz</code></a> made
their first contribution in <a
href="https://redirect.github.com/caronc/apprise/pull/1707">caronc/apprise#1707</a></li>
<li><a
href="https://github.com/milos-korenciak"><code>@​milos-korenciak</code></a>
made their first contribution in <a
href="https://redirect.github.com/caronc/apprise/pull/1711">caronc/apprise#1711</a></li>
<li><a href="https://github.com/vaibhav8a"><code>@​vaibhav8a</code></a>
made their first contribution in <a
href="https://redirect.github.com/caronc/apprise/pull/1716">caronc/apprise#1716</a></li>
<li><a
href="https://github.com/signalgridco"><code>@​signalgridco</code></a>
made their first contribution in <a
href="https://redirect.github.com/caronc/apprise/pull/1710">caronc/apprise#1710</a></li>
<li><a
href="https://github.com/csanadymiklos"><code>@​csanadymiklos</code></a>
made their first contribution in <a
href="https://redirect.github.com/caronc/apprise/pull/1715">caronc/apprise#1715</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/caronc/apprise/compare/v1.13.0...v1.13.1">https://github.com/caronc/apprise/compare/v1.13.0...v1.13.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/caronc/apprise/commit/cf4da87eab713370a62d7a4f9ca64dcd3bffdf61"><code>cf4da87</code></a>
bumped version to v1.13.1</li>
<li><a
href="https://github.com/caronc/apprise/commit/0386b1cc093b3c55cfcebf4bdce17fc4309e0bd7"><code>0386b1c</code></a>
Add Lauther notification support (<a
href="https://redirect.github.com/caronc/apprise/issues/1715">#1715</a>)</li>
<li><a
href="https://github.com/caronc/apprise/commit/3299eb37aa60ed729f935f039b79a9f9786e7424"><code>3299eb3</code></a>
Add Signalgrid notification support (<a
href="https://redirect.github.com/caronc/apprise/issues/1710">#1710</a>)</li>
<li><a
href="https://github.com/caronc/apprise/commit/e05331ba9b5d2ace834b23e49728065ffebcb377"><code>e05331b</code></a>
relaxed some noisy logging</li>
<li><a
href="https://github.com/caronc/apprise/commit/8f73f6a0cdb83e6187624715a12e8f36dd8a1bbc"><code>8f73f6a</code></a>
Fixes intermittent test failures for matrix on py39 (<a
href="https://redirect.github.com/caronc/apprise/issues/1717">#1717</a>)</li>
<li><a
href="https://github.com/caronc/apprise/commit/0af1bb2097dad2557f55a523d09269341fa3fdfb"><code>0af1bb2</code></a>
docs: fix typo recomended -&gt; recommended (<a
href="https://redirect.github.com/caronc/apprise/issues/1716">#1716</a>)</li>
<li><a
href="https://github.com/caronc/apprise/commit/8df288a1824711bc6b1bdb59c1b5b356e4da3fd5"><code>8df288a</code></a>
Allow periods in Pingram API keys (JWT format) (<a
href="https://redirect.github.com/caronc/apprise/issues/1714">#1714</a>)</li>
<li><a
href="https://github.com/caronc/apprise/commit/aa7221b7cabe757e000a1e6e019fc8f91ec517a0"><code>aa7221b</code></a>
Allow Py 3.13+ (<a
href="https://redirect.github.com/caronc/apprise/issues/1711">#1711</a>)</li>
<li><a
href="https://github.com/caronc/apprise/commit/e93c9360c5ae2ed729a282bc454f6ceeb4abd9e1"><code>e93c936</code></a>
Fixes Gento Linux issue with memory based attachment handling (<a
href="https://redirect.github.com/caronc/apprise/issues/1709">#1709</a>)</li>
<li><a
href="https://github.com/caronc/apprise/commit/4e6f4b117fa356f7ba0a076080c4927fcf78bc47"><code>4e6f4b1</code></a>
typofix in matrix:// label (<a
href="https://redirect.github.com/caronc/apprise/issues/1707">#1707</a>)</li>
<li>See full diff in <a
href="https://github.com/caronc/apprise/compare/v1.13.0...v1.13.1">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-03 01:14:41 -04:00
CaliBrain 9452ebc70d fix(bypass): stop handing solvers DDoS-Guard's ?check=1 probe URL (#1300)
html_get_page follows Anna's Archive redirects by hand, and DDoS-Guard's
gate
answers /search with a 302 to the same path plus `check=1`. The follower
walks
that handshake by reassigning `current_url`, so every downstream handoff
- the
403 branch, the 503-challenge branch, both redirect-loop rescues -
passed the
*probe* URL to the bypasser rather than the page we actually wanted.

A solver opens that in a fresh browser holding none of the cookies the
probe
exists to collect, so DDoS-Guard cannot verify it automatically and
serves the
manual CAPTCHA page that nothing can solve. The #1292 log is exactly
that: a 403
handed off on `&check=1`, FlareSolverr answering "Challenge solved!",
and a
4721-byte DDOS-GUARD captcha page coming back.

- `_solvable_url()` strips the probe parameter, applied at the single
choke point
in `_run_bypasser` so all four handoffs are covered. Scoped to the hosts
whose
redirects we follow manually; a URL without the parameter is returned by
  identity, so nothing else is re-encoded.

The same reports showed three further defects, all of which stand
whatever the
host was reacting to:

- The external bypasser logged that the solve had not cleared the
protection and
then returned the challenge page as a success. That skipped the one
recovery
left - get_bypassed_page's retry-and-rotate loop, where the next mirror
is a
different DDoS-Guard host - and filed the captcha page's own __ddg
cookies as
that host's clearance, to be replayed on every later request. It now
raises
  ChallengeNotSolvedError before storing anything.

- "Check that the bypasser is reachable and working" was the one piece
of advice
guaranteed to waste the reporter's time: it was reachable, it ran a full
solve,
and it returned a captcha. ChallengeNotSolvedError carries the marker so
the
  search layer can name the host as the cause instead of the bypasser.

- The untabled-page fingerprint logged `attempt_url`, which
html_get_page has
since rotated past. The #1298 bundle reported the page against
annas-archive.gl
when the body had come from .pk - the triage cost #1289 added the line
to
remove. The search now asks for the response URL and logs that. Its
give-up
shape is the tuple ("", url), which is truthy, so the exhaustion check
reads
  the body rather than the response.

Regression fixtures are built from the pages in the reports. The two
behavioural
handoff tests were checked against the unfixed code: both fail there,
reproducing
the reporter's log line verbatim.

Refs #1292
Refs #1298
2026-09-02 16:12:52 -04:00
CaliBrain d3f4ccd79a seleniumbase==4.53.5 (#1299)
replaces #1296
2026-09-02 15:48:48 -04:00
CaliBrain cb690b45b8 fix(prowlarr): rank releases by author instead of querying for it (#1293) (#1295)
MyAnonamouse is the only indexer Shelfmark treats as enriched, and it
alone was sent {title} {author} while every other indexer got the title
on its own. MAM matches all search terms conjunctively, so whenever the
metadata provider spelled the author differently to the tracker -
Hardcover says Timothy Ferriss, MAM lists Tim Ferriss - the search came
back empty and the UI reported No releases found for this book, with the
release sitting on the tracker the whole time.

The enriched flag is a statement about responses: MAM returns clean
author and bookTitle attributes, which is why it earns format detection
and preferential ordering. Using that same flag to shape the request is
the actual defect, and it is why turning the flag off recovers the
search but takes format detection down with it.

So the query is title-only for every indexer now, and the author orders
the results rather than narrowing them. MAM already hands us its author
field, so agreement is judged on data we hold instead of by an AND we
cannot control. The ranking is three-way on purpose - agrees, no
metadata, disagrees - so an indexer reporting no author does not sort
below one reporting the wrong author.

A wrong verdict costs a release its position, never its visibility: a
transliteration such as Dostoevsky against Dostoyevsky sorts last
instead of vanishing. That is what makes the loose token comparison safe
to ship without a tuning knob.

Falling back to a title-only query on zero results was the alternative.
It only rescues total failure - if two of six editions happen to use the
provider's spelling, the search returns those two, no fallback fires,
and the user quietly gets a truncated list. It also spends a round trip
inside the search deadline and stacks a retry on an indexer that may
still be solving a challenge (#1249).

Manual queries skip author ranking: they are the user's own words and
should not be reordered against the metadata they were typed to
override.
2026-09-01 12:59:33 -04:00
CaliBrain 3d7ea40088 fix(search): reach the server's deadline, query one author (#1285, #1252) (#1287)
Two independent reasons a working search reported failure to the user.

1. The client gave up before the server did (#1285)

`/api/releases` bounds one release search with RELEASE_SEARCH_TIMEOUT
(default
300s) and answers a spent budget with a sentence naming the real cause -
the
machinery added for #1276. The frontend then aborted the direct_download
search
at a hard-coded 180s, so it always won the race: the user saw "Request
timed
out. Check your network connection or proxy configuration." instead, and
raising RELEASE_SEARCH_TIMEOUT changed nothing they could observe, the
180s
being baked into the hashed bundle inside the image.

- /api/config reports the effective (clamped) budget, and the client
derives its
  abort from it plus a margin, so the server always answers first.
- Direct-mode search shows what the server actually said. Every non-auth
failure
was relabelled "Unable to reach download source. Network may be
restricted or
mirrors blocked.", which discarded the explanation and blamed the user's
network. ApiResponseError now carries `serverMessage`, set only when the
server
  explained itself, so the status-line placeholder still falls back.

Two latency fixes for the cost that made the timeout reachable at all:

- Fetch each distinct AA search URL once per search. The language-filter
retry
re-runs every title variant, and with DIRECT_DOWNLOAD_LANGUAGE_FROM_PATH
on
both passes build a byte-identical URL - behind DDoS-Guard each repeat
is a
  fresh browser solve.
- Drop the solve-only bypass method. `_bypass_method_cdp_gui_click`
opens with
exactly that call and returns the moment it works, so the entry ahead of
it
could only repeat the half that had already failed, plus the backoff
before
the method that does work started. Reported at 0/19 successes and ~5.5s
of
  each ~26s solve against DDoS-Guard.

2. The query carried every contributor, not one author (#1252)

`_pick_search_author` returned `book.search_author` verbatim while the
authors[]
fallback beside it deliberately narrowed to the first name before a
comma. Both
fields routinely arrive holding every contributor joined with ", ": the
frontend
builds `book.author` as `authors.join(', ')` for display
(bookTransformers.ts)
and the release modal sends that display string straight back as the
`author`
parameter, and `browse_record_to_book_metadata` and the manual-search
branch
both split the joined text into `authors` while still passing the
unsplit string
as `search_author`, so the split was never used.

A book whose metadata lists translators was therefore searched for as

    Blindness Jose Saramago, Giovanni Pontiero, <persian translator>

which matches nothing on Anna's Archive. The bypass succeeds, the search
comes
back empty, and the user is told the book has no releases.

Narrowed in one place, `search_plan.first_author`, so the two branches
cannot
drift apart again, and applied to the IRC source, which built its query
with the
same verbatim preference. Hardcover is unaffected: it already sets
`search_author` from `_simplify_author_for_search(authors[0])`, which
resolves
"Last, First" itself and never yields a multi-author string.
2026-09-01 12:38:57 -04:00
CaliBrain 633004ecf0 fix(search): stop reading real Anna's Archive pages as unsolved challenges (#1294)
`_looks_like_challenge_page` substring-matched "ddos-guard"/"cloudflare"
over
the whole document. DDoS-Guard-fronted sites carry those strings on
their own
pages - Anna's Archive ships a `DDOS-GUARD` comment in the inline JS it
serves
on every page - so every real AA response that was not a results table
was
reported as an unsolved protection challenge, sending users off to fix a
bypasser that had just succeeded.

Measured against live pages: a served AA page (HTTP 200) is 182,685
bytes and
matched the old detector; the real interstitial is 902 bytes.

- `_looks_like_challenge_page` now delegates to the shared
`challenge_marker()`,
whose 64 KB cap is what separates a few-KB interstitial from the page
behind
it. `download/http.py` already used it; this module carried an unguarded
  private copy.
- `_looks_like_aa_page` is checked ahead of the challenge branch. A
genuine
interstitial carries no AA markers, so nothing actually blocked leaks
through.

Also adds the diagnostics whose absence made #1289 guesswork: the debug
bundle
carries no response bodies, so "unsolved protection challenge" and
FlareSolverr's
"Challenge solved!" were indistinguishable after the fact.

- `_log_untabled_search_page()` fingerprints the one ambiguous shape at
INFO -
size, size-cap verdict, AA markers, challenge marker - with a bounded
700-char
  head at DEBUG. Best-effort: it swallows its own errors.
- The external bypasser records what it actually returned, and warns
when it
  reports success while handing back a challenge page.

Regression tests use fixtures built from the live pages rather than
invented
ones; the previous fixtures were two-line synthetic pages with no
"ddos-guard"
substring, which is why nothing caught this.

Closes #1289
Closes #1292
2026-09-01 11:19:01 -04:00
dependabot[bot] c06b8ce8ef build(deps): bump the python-deps group with 3 updates (#1288)
Bumps the python-deps group with 3 updates:
[seleniumbase](https://github.com/seleniumbase/SeleniumBase),
[prek](https://github.com/j178/prek) and
[ruff](https://github.com/astral-sh/ruff).

Updates `seleniumbase` from 4.52.3 to 4.52.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.52.4 - Add Remote WebDriver Timeout setting</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/cf4af2a92414010ca49c5ba14e59cf73dc8badea">Add
REMOTE_WEBDRIVER_TIMEOUT setting for Remote WebDriver HTTP
requests</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>Add REMOTE_WEBDRIVER_TIMEOUT setting for Remote WebDriver HTTP
requests by <a
href="https://github.com/TaylorMcGinnis"><code>@​TaylorMcGinnis</code></a>
in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4473">seleniumbase/SeleniumBase#4473</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.52.3...v4.52.4">https://github.com/seleniumbase/SeleniumBase/compare/v4.52.3...v4.52.4</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/5879697828e55a6c6ef8f7436c01023e3af92108"><code>5879697</code></a>
Version 4.52.4</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/2182f3c9ece14ac0cc1d4fdb4c24470e0e0ac63e"><code>2182f3c</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4473">#4473</a>
from TaylorMcGinnis/remote-webdriver-timeout</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/ffe06d572308b7cca77644dd22f7560dd3c4eda4"><code>ffe06d5</code></a>
Update GitHub Actions</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/cf4af2a92414010ca49c5ba14e59cf73dc8badea"><code>cf4af2a</code></a>
Add REMOTE_WEBDRIVER_TIMEOUT setting for Remote WebDriver HTTP
requests</li>
<li>See full diff in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.52.3...v4.52.4">compare
view</a></li>
</ul>
</details>
<br />

Updates `prek` from 0.4.14 to 0.5.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/releases">prek's
releases</a>.</em></p>
<blockquote>
<h2>0.5.0</h2>
<h2>Release Notes</h2>
<p>Released on 2026-08-27.</p>
<h3>Highlights</h3>
<h4>Choose where hook toolchains come from</h4>
<p><code>language_version</code> now accepts a source
<code>preference</code> alongside the version
<code>request</code>, letting you control where prek looks for a
compatible toolchain when
it creates a hook environment. Use <code>managed</code> (the default) or
<code>system</code> to choose
which source prek tries first while still allowing fallback and
downloads. Use
<code>only-managed</code> or <code>only-system</code> to require one
source.</p>
<p>For example, this local Ruff hook requires a Python 3.12 toolchain
managed by
prek:</p>
<pre lang="yaml"><code>repos:
  - repo: local
    hooks:
      - id: ruff
        name: ruff
        language: python
        entry: ruff check
        additional_dependencies: [ruff]
        language_version:
          request: &quot;3.12&quot;
          preference: only-managed
</code></pre>
<p>With <code>only-managed</code>, prek reuses a compatible toolchain
from its managed store
or downloads one when needed. It never falls back to Python from
<code>PATH</code>, an OS
package manager, or a version manager, so toolchain selection does not
depend on
the developer or CI machine's external environment.</p>
<p>Existing scalar values such as <code>language_version:
&quot;3.12&quot;</code> continue to work. See
<a
href="https://prek.j178.dev/0.5.0/languages/#toolchain-management-and-language_version">toolchain
management and <code>language_version</code></a>
for the full source-selection behavior. (<a
href="https://redirect.github.com/j178/prek/pull/2613">#2613</a>)</p>
<h3>Breaking changes</h3>
<p>The breaking changes in this release are mostly small cleanups, and
most users should not be affected.</p>
<ul>
<li>Group names can no longer start with <code>@</code>. This prefix is
now reserved for special group selectors such as the new
<code>@ungrouped</code> selector. (<a
href="https://redirect.github.com/j178/prek/pull/2617">#2617</a>)</li>
<li><code>PREK_MAX_CONCURRENCY</code> has been removed. Use
<code>PREK_CONCURRENT_HOOKS</code> and
<code>PREK_CONCURRENT_BATCHES</code> to control hook and per-hook batch
concurrency separately. (<a
href="https://redirect.github.com/j178/prek/pull/2620">#2620</a>)</li>
<li>The top-level <code>prek init-template-dir</code> command has been
removed. Use <code>prek util init-template-dir</code>, or <code>prek
init-templatedir</code> for drop-in compatibility with
<code>pre-commit</code>. (<a
href="https://redirect.github.com/j178/prek/pull/2623">#2623</a>)</li>
<li><code>prek auto-update</code> has been removed. Use <code>prek
update</code>, or <code>prek autoupdate</code> for drop-in compatibility
with <code>pre-commit</code>. (<a
href="https://redirect.github.com/j178/prek/pull/2619">#2619</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/blob/master/CHANGELOG.md">prek's
changelog</a>.</em></p>
<blockquote>
<h2>0.5.0</h2>
<p>Released on 2026-08-27.</p>
<h3>Highlights</h3>
<h4>Choose where hook toolchains come from</h4>
<p><code>language_version</code> now accepts a source
<code>preference</code> alongside the version
<code>request</code>, letting you control where prek looks for a
compatible toolchain when
it creates a hook environment. Use <code>managed</code> (the default) or
<code>system</code> to choose
which source prek tries first while still allowing fallback and
downloads. Use
<code>only-managed</code> or <code>only-system</code> to require one
source.</p>
<p>For example, this local Ruff hook requires a Python 3.12 toolchain
managed by
prek:</p>
<pre lang="yaml"><code>repos:
  - repo: local
    hooks:
      - id: ruff
        name: ruff
        language: python
        entry: ruff check
        additional_dependencies: [ruff]
        language_version:
          request: &quot;3.12&quot;
          preference: only-managed
</code></pre>
<p>With <code>only-managed</code>, prek reuses a compatible toolchain
from its managed store
or downloads one when needed. It never falls back to Python from
<code>PATH</code>, an OS
package manager, or a version manager, so toolchain selection does not
depend on
the developer or CI machine's external environment.</p>
<p>Existing scalar values such as <code>language_version:
&quot;3.12&quot;</code> continue to work. See
<a
href="https://prek.j178.dev/0.5.0/languages/#toolchain-management-and-language_version">toolchain
management and <code>language_version</code></a>
for the full source-selection behavior. (<a
href="https://redirect.github.com/j178/prek/pull/2613">#2613</a>)</p>
<h3>Breaking changes</h3>
<p>The breaking changes in this release are mostly small cleanups, and
most users should not be affected.</p>
<ul>
<li>Group names can no longer start with <code>@</code>. This prefix is
now reserved for special group selectors such as the new
<code>@ungrouped</code> selector. (<a
href="https://redirect.github.com/j178/prek/pull/2617">#2617</a>)</li>
<li><code>PREK_MAX_CONCURRENCY</code> has been removed. Use
<code>PREK_CONCURRENT_HOOKS</code> and
<code>PREK_CONCURRENT_BATCHES</code> to control hook and per-hook batch
concurrency separately. (<a
href="https://redirect.github.com/j178/prek/pull/2620">#2620</a>)</li>
<li>The top-level <code>prek init-template-dir</code> command has been
removed. Use <code>prek util init-template-dir</code>, or <code>prek
init-templatedir</code> for drop-in compatibility with
<code>pre-commit</code>. (<a
href="https://redirect.github.com/j178/prek/pull/2623">#2623</a>)</li>
<li><code>prek auto-update</code> has been removed. Use <code>prek
update</code>, or <code>prek autoupdate</code> for drop-in compatibility
with <code>pre-commit</code>. (<a
href="https://redirect.github.com/j178/prek/pull/2619">#2619</a>)</li>
</ul>
<h3>Enhancements</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/j178/prek/commit/67f85359486c57b0fc145ae948283713bf33bf94"><code>67f8535</code></a>
Bump version to 0.5.0 (<a
href="https://redirect.github.com/j178/prek/issues/2631">#2631</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/4546befaacb22177be1a7590fb944cad0664ad73"><code>4546bef</code></a>
Remove legacy <code>init-template-dir</code> command (<a
href="https://redirect.github.com/j178/prek/issues/2623">#2623</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/e63bd4be1ead8c09effeac5c451c5efd94e1f643"><code>e63bd4b</code></a>
Remove hook marker schema 0 (<a
href="https://redirect.github.com/j178/prek/issues/2622">#2622</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/8c433ae73124cef61c390217e0163adbb6a17cd9"><code>8c433ae</code></a>
Remove config-tracking cache bootstrap (<a
href="https://redirect.github.com/j178/prek/issues/2621">#2621</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/62ca460aea7d5e24ca27e79cd1128ab91ce396c1"><code>62ca460</code></a>
Remove <code>PREK_MAX_CONCURRENCY</code> (<a
href="https://redirect.github.com/j178/prek/issues/2620">#2620</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/e2468eae017a0cb5e68fd54f96e6b66f5d6db598"><code>e2468ea</code></a>
Remove legacy update aliases (<a
href="https://redirect.github.com/j178/prek/issues/2619">#2619</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/795c3a46b3c1b20a343a5fe8ec0d1b9fea79ade9"><code>795c3a4</code></a>
Group run options in CLI help (<a
href="https://redirect.github.com/j178/prek/issues/2629">#2629</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/b27eb6edf4779994f4e484be605119291f858fc6"><code>b27eb6e</code></a>
Document prek run architecture (<a
href="https://redirect.github.com/j178/prek/issues/2630">#2630</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/7dd9aa9149854bb0a37bde6ed64e3d1b42c46fdb"><code>7dd9aa9</code></a>
Avoid persisting docs workflow credentials (<a
href="https://redirect.github.com/j178/prek/issues/2627">#2627</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/23815bdd83ce766e93647a98cfc091ec15b0d9ba"><code>23815bd</code></a>
Remove the <code>@builtin</code> group selector (<a
href="https://redirect.github.com/j178/prek/issues/2628">#2628</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/j178/prek/compare/v0.4.14...v0.5.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `ruff` from 0.16.4 to 0.16.5
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/releases">ruff's
releases</a>.</em></p>
<blockquote>
<h2>0.16.5</h2>
<h2>Release Notes</h2>
<p>Released on 2026-08-27.</p>
<h3>Preview features</h3>
<ul>
<li>Allow rules without codes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28049">#28049</a>)</li>
<li>Introduce category selectors (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27666">#27666</a>)</li>
<li>Update preview default rules and categories (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27877">#27877</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>[<code>flake8-async</code>] Detect blocking generic HTTP requests
(<code>ASYNC210</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28024">#28024</a>)</li>
<li>[<code>flake8-datetimez</code>] Allow timezone-safe
<code>strptime</code> chains (<code>DTZ007</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28023">#28023</a>)</li>
<li>[<code>flake8-simplify</code>] Respect side effects in
<code>lambda</code> defaults (<code>SIM401</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28000">#28000</a>)</li>
</ul>
<h3>Server</h3>
<ul>
<li>Fix duplicated &quot;of&quot; in <code>ClientOptions</code> doc
comment (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27978">#27978</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Document rule acceptance guidelines (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27910">#27910</a>)</li>
<li>Document the new category selectors (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27906">#27906</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/AlexWaygood"><code>@​AlexWaygood</code></a></li>
<li><a href="https://github.com/sharkdp"><code>@​sharkdp</code></a></li>
<li><a
href="https://github.com/jelle-openai"><code>@​jelle-openai</code></a></li>
<li><a
href="https://github.com/charliermarsh"><code>@​charliermarsh</code></a></li>
<li><a href="https://github.com/ntBre"><code>@​ntBre</code></a></li>
<li><a
href="https://github.com/aarushkandukoori"><code>@​aarushkandukoori</code></a></li>
</ul>
<h2>Install ruff 0.16.5</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://releases.astral.sh/github/ruff/releases/download/0.16.5/ruff-installer.sh
| sh
</code></pre>
<h3>Install prebuilt binaries via powershell script</h3>
<pre lang="sh"><code>powershell -ExecutionPolicy Bypass -c &quot;irm
https://releases.astral.sh/github/ruff/releases/download/0.16.5/ruff-installer.ps1
| iex&quot;
</code></pre>
<h2>Download ruff 0.16.5</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's
changelog</a>.</em></p>
<blockquote>
<h2>0.16.5</h2>
<p>Released on 2026-08-27.</p>
<h3>Preview features</h3>
<ul>
<li>Allow rules without codes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28049">#28049</a>)</li>
<li>Introduce category selectors (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27666">#27666</a>)</li>
<li>Update preview default rules and categories (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27877">#27877</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>[<code>flake8-async</code>] Detect blocking generic HTTP requests
(<code>ASYNC210</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28024">#28024</a>)</li>
<li>[<code>flake8-datetimez</code>] Allow timezone-safe
<code>strptime</code> chains (<code>DTZ007</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28023">#28023</a>)</li>
<li>[<code>flake8-simplify</code>] Respect side effects in
<code>lambda</code> defaults (<code>SIM401</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/28000">#28000</a>)</li>
</ul>
<h3>Server</h3>
<ul>
<li>Fix duplicated &quot;of&quot; in <code>ClientOptions</code> doc
comment (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27978">#27978</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Document rule acceptance guidelines (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27910">#27910</a>)</li>
<li>Document the new category selectors (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27906">#27906</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/AlexWaygood"><code>@​AlexWaygood</code></a></li>
<li><a href="https://github.com/sharkdp"><code>@​sharkdp</code></a></li>
<li><a
href="https://github.com/jelle-openai"><code>@​jelle-openai</code></a></li>
<li><a
href="https://github.com/charliermarsh"><code>@​charliermarsh</code></a></li>
<li><a href="https://github.com/ntBre"><code>@​ntBre</code></a></li>
<li><a
href="https://github.com/aarushkandukoori"><code>@​aarushkandukoori</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ruff/commit/9e4938c4a60bed3e87a11ee1e1db1bd23f4d964a"><code>9e4938c</code></a>
Bump 0.16.5 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28110">#28110</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/aad0e909ef1390f4b2a3ba8aa0a67fb8ea5cbacd"><code>aad0e90</code></a>
Allow rules without codes (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28049">#28049</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/5fdab73c5052350400c36b08c5d7710210343bc4"><code>5fdab73</code></a>
Update preview default rules and categories (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27877">#27877</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/29c8e5b2d0a46eb7dc4ff11c1b0a0dc5ccea52e4"><code>29c8e5b</code></a>
Document rule acceptance guidelines (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27910">#27910</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/50a4d7fd106603a5616b01ac3bef3306252b248f"><code>50a4d7f</code></a>
Document the new category selectors (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27906">#27906</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/ada87950ea188f882f69b7bd6e2213a9696e3ee2"><code>ada8795</code></a>
Introduce category selectors (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27666">#27666</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/d8947238863b61922bfc83f07edcc697c1cc07c0"><code>d894723</code></a>
[ty] Infer lambda parameters through callable type aliases (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28109">#28109</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/2685fdebbcf9938736fed8c45886a629f9c99a06"><code>2685fde</code></a>
[ty] Narrow functional enum members in <code>==</code> and
<code>match</code> (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28103">#28103</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/efcffd2178ce62e9951a53d4c50cadc225a0cfec"><code>efcffd2</code></a>
[ty] Intersection simplifications with subtype-related generic
specialization...</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/eb780488037504e11f145ed778654fd8a825028b"><code>eb78048</code></a>
[ty] Bump ecosystem-analyzer for HTML escaping (<a
href="https://redirect.github.com/astral-sh/ruff/issues/28104">#28104</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ruff/compare/0.16.4...0.16.5">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-01 11:13:24 -04:00
Nicholas Velten 69ff0d6a78 fix: trim a credit list in search_author to the first name (#1290)
Fixes #1252 for the case in the second report.

`_pick_search_author` returns `search_author` untouched but trims
`authors[0]` to its first comma-separated name. So the same credit list
searches differently depending on which field carries it:

```
via authors[0]     -> "Blindness Jose Saramago"
via search_author  -> "Blindness Jose Saramago, Giovanni Pontiero, Zohreh Eftekhari"
```

Anna's Archive answers the second one with nothing. That is the query in
@theDoz12's log, and it explains the shape of the report: the bypass
succeeds, the search runs, and the UI still says no releases. Nothing in
the download path is broken, the query simply cannot match.

Measured against live AA on 1.3.14, same book, same source, only the
field carrying the author changed:

| query | releases |
| --- | --- |
| `Blindness Jose Saramago, Giovanni Pontiero, Zohreh Eftekhari` | 0 |
| `Blindness Jose Saramago` | 49 |
| `Blindness` | 50 |

With the patch the second form is produced from either field, and the
same search returns 49.

Three regression tests added, including one that asserts both fields
yield the same query. On `tests/core/test_search_plan.py` the run goes
from 5 failures to 3; the 3 that remain are the language tests, which
fail identically with and without this change on my machine.

Worth saying what this does not cover: the first report in that issue
ends with `Found 2 releases via ISBN` and still shows nothing, so that
one is a different fault further along. I could not reproduce it here.
2026-09-01 11:08:39 -04:00
Nathan H 3937ae119b feat(homepage): Always show controls (#1269)
I found using the main search menu frustrating. Often times, I would
type in what I want, then select the category, only for it to get
erased. And the menu closing over and over was distracting. So this PR
makes the buttons stick around permanently and it removes the search
field text changing with each button press.

Obviously, this is just what I want, but I figured I'd at least put a PR
up for it.

<img width="1017" height="423" alt="image"
src="https://github.com/user-attachments/assets/7b84fd69-d6d3-4749-842f-e04a6e792ccc"
/>
<img width="682" height="418" alt="image"
src="https://github.com/user-attachments/assets/82a251ee-cb4a-488f-b1ba-45fb2bb6714d"
/>
2026-09-01 10:51:49 -04:00
Jorge Lima d7fe28595c fix(bypass): wait for the solved page before reading its source (#1286)
Follow-up to #1276 with a measurement from the instance I reported
there. v1.3.13 solves the challenge again, but on my setup the solve was
being thrown away immediately afterwards:

```
19:26:08 Bypass successful using _bypass_method_cdp_gui_click
19:26:16 Bypass failed (attempt 1/10): TimeoutError: Time ran out while waiting for: {html}
```

`_get()` ends with `return await page.get_page_source()`, which is
`find("html", timeout=1)` in SeleniumBase. One second is enough for a
page that is already sitting on its content, but Anna's Archive answers
a cleared check with a redirect to the real page, so the document is not
there yet. The solve is discarded, the whole attempt restarts, and the
extra requests are what earn the 429 that `note_rate_limited()` then
parks the host for — 120 s, then 300 s.

## Change

`_read_page_source()` waits for the document itself, with a
`BYPASS_PAGE_SOURCE_TIMEOUT` setting (default 20 s, min 1, max 120) in
Direct Download → Cloudflare Bypass, next to the existing bypasser
timeouts.

## Measured on a live instance

I patched the wait in the running container (`find("html", timeout=1)` →
`timeout=20` in the installed seleniumbase, which is the same effect as
this PR) and re-ran the same searches on the same host, k3s behind a
Surfshark WireGuard exit, internal bypasser, v1.3.13:

| | 1 s wait | 20 s wait |
|---|---|---|
| `Time ran out while waiting for: {html}` | one per solve | none |
| 429 backoffs | 2 (120 s, then 300 s) | none |
| Search for a book AA has | 199 s and 200 s, both errored | 61 s, 2
epub releases |

A download after that took 5 s from LibGen, so the search was the whole
cost.

## Tests

Two tests in `tests/bypass/test_bypass_budgets.py`, the file already
covering #1276: a page that needs longer than a second still yields its
HTML, and `BYPASS_PAGE_SOURCE_TIMEOUT` overrides the default.

`uv run pytest tests/ --ignore=tests/e2e`: 2848 passed, 47 skipped. Ruff
check and format clean. The docs table is auto-generated, but running
`scripts/generate_env_docs.py` here rewrote unrelated entries (Newznab,
BOOK_LANGUAGE), so I added only the new entry by hand in the generator's
format rather than commit that churn.

One thing I could not judge from outside: whether 20 s is the right
default for hosts other than AA. It only costs anything when a solve
would otherwise be discarded, but I have measured it on one site.
2026-08-30 19:17:15 -04:00
dependabot[bot] 68c0e83330 build(deps): bump the python-deps group with 2 updates (#1277)
Bumps the python-deps group with 2 updates:
[gunicorn](https://github.com/benoitc/gunicorn) and
[seleniumbase](https://github.com/seleniumbase/SeleniumBase).

Updates `gunicorn` from 26.1.0 to 26.2.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/benoitc/gunicorn/releases">gunicorn's
releases</a>.</em></p>
<blockquote>
<h2>gunicorn 26.2.0</h2>
<p>Cleartext HTTP/2 lands, and an HTTP/2 security fix.</p>
<h2>Cleartext HTTP/2 (h2c)</h2>
<p><code>http2_cleartext</code> accepts <code>prior-knowledge</code>,
<code>upgrade</code>, <code>both</code> or <code>off</code> (the
default). Prior knowledge serves a connection that opens with the HTTP/2
preface; <code>upgrade</code> honours an HTTP/1.1 <code>Upgrade:
h2c</code> request. Both work on the
gthread, gevent and asgi workers.</p>
<p>This is for deployments where TLS is terminated by a proxy that
speaks HTTP/2
upstream, so the hop into gunicorn no longer drops to HTTP/1.1. Only
peers in
<code>forwarded_allow_ips</code> are considered; everyone else is served
HTTP/1.x exactly
as if the setting were off. Each mechanism is enabled separately, so
turning one
on does not turn the other on.</p>
<p>Do not expose a cleartext HTTP/2 port to the internet.</p>
<h2>Security</h2>
<p><code>HTTP2Request</code> built its headers straight from the stream,
so nothing the HTTP/1
path enforces applied over HTTP/2: the underscore and
<code>header_map</code> policy,
duplicate <code>Host</code> and <code>Content-Type</code>, control
characters in values, and the
<code>forwarded_allow_ips</code> trust gate. An untrusted client could
set <code>SCRIPT_NAME</code>
and forge <code>HTTP_*</code> entries in the WSGI environ, and decide
<code>wsgi.url_scheme</code>
through <code>:scheme</code>. Both request classes now share one policy
mixin, and the
scheme comes from the transport.</p>
<p>If you serve HTTP/2, this is the reason to upgrade.</p>
<h2>Other HTTP/2 fixes</h2>
<p>WSGI responses were buffered whole before anything was sent; they
stream now.
HEAD, 204 and 304 no longer carry a body. Events read while blocked on a
flow-control window were discarded, losing requests and body data
outright.
<code>sendfile()</code> is refused on HTTP/2 responses rather than
bypassing framing.</p>
<h2>Request bodies dropped on Upgrade requests</h2>
<p>On the ASGI worker with the fast parser, any request carrying an
<code>Upgrade</code>
header reached the application with an empty body, whatever the header's
value
and with HTTP/2 switched off entirely. Fixed in
<code>gunicorn_h1c</code> 0.6.9, which the
<code>fast</code> extra now requires.</p>
<p>Full changelog: <a
href="https://gunicorn.org/news/">https://gunicorn.org/news/</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/benoitc/gunicorn/commit/36f2a3c1b80dfa41d70859d12c5bfbbdc23a3c38"><code>36f2a3c</code></a>
gunicorn 26.2.0</li>
<li><a
href="https://github.com/benoitc/gunicorn/commit/cbba3505f423bfb91af3a87e49ed9d232f39a8fe"><code>cbba350</code></a>
test: cover the h2c edge paths that had none</li>
<li><a
href="https://github.com/benoitc/gunicorn/commit/988541112ebcf3f795c020fc394aa7eed75f9f53"><code>9885411</code></a>
Merge pull request <a
href="https://redirect.github.com/benoitc/gunicorn/issues/3703">#3703</a>
from cormier/fix-inconsistency-in-control-socket-docs</li>
<li><a
href="https://github.com/benoitc/gunicorn/commit/86f0919806a2d4d4cce376cc2088352e7643b139"><code>86f0919</code></a>
Merge pull request <a
href="https://redirect.github.com/benoitc/gunicorn/issues/3704">#3704</a>
from methane/doc-wsgi-h1c</li>
<li><a
href="https://github.com/benoitc/gunicorn/commit/585355122efe736946b977c5605e404ff2d6ddd4"><code>5853551</code></a>
Merge pull request <a
href="https://redirect.github.com/benoitc/gunicorn/issues/3712">#3712</a>
from Rotzbua/patch-1</li>
<li><a
href="https://github.com/benoitc/gunicorn/commit/7bce87e2aa29a4a794eb2b113ff811cad6a80736"><code>7bce87e</code></a>
Merge pull request <a
href="https://redirect.github.com/benoitc/gunicorn/issues/3700">#3700</a>
from benoitc/fix/sponsor-logo-path</li>
<li><a
href="https://github.com/benoitc/gunicorn/commit/972dfb03b110c430712c32a3d92ef6397ff8eff6"><code>972dfb0</code></a>
Merge pull request <a
href="https://redirect.github.com/benoitc/gunicorn/issues/3690">#3690</a>
from melbinjp/docs/contributing-settings-path</li>
<li><a
href="https://github.com/benoitc/gunicorn/commit/7b3f16be8d9cc051538b7f0b58b236b37c9550f8"><code>7b3f16b</code></a>
Merge pull request <a
href="https://redirect.github.com/benoitc/gunicorn/issues/3711">#3711</a>
from benoitc/docs/http2-changelog</li>
<li><a
href="https://github.com/benoitc/gunicorn/commit/5bf237c0c7ef5bcdc63046645a17d6bafd609a34"><code>5bf237c</code></a>
http2: require gunicorn_h1c 0.6.9 and drop the upgrade body
workaround</li>
<li><a
href="https://github.com/benoitc/gunicorn/commit/7cf03385c574228e28c4952fd410ed2df02acc94"><code>7cf0338</code></a>
test: skip the fast-parser cases when gunicorn_h1c is absent</li>
<li>Additional commits viewable in <a
href="https://github.com/benoitc/gunicorn/compare/26.1.0...26.2.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `seleniumbase` from 4.52.2 to 4.52.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.52.3 - MCP Server: Patch 1</h2>
<h2>MCP Server: Patch 1</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/9bdc1133d096562111d3cc6465c6b0cd5dbfc38d">Fix
the MCP Server on Python versions less than 3.14</a>
--&gt; This resolves <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4471">seleniumbase/SeleniumBase#4471</a>
--&gt; (Due to this bug, the MCP Server only worked on Python 3.14+)
--&gt; (Caused by a missing line: <code>from __future__ import
annotations</code>)</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/0e14a09f2d2f2e62a85bacc890b1d9d48b9a0c79">Update
logging messages</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/1584e5b1b83f7177c59810817942150d1ed3ecab">Update
the docs for MCP servers</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/213580314cb106bcb14d857289eb494395f491ae">Refresh
Python dependencies</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/405c7c68599108fc99d1cf929e01b17d2c62cd7a">Update
examples</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>MCP Server: Patch 1 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4472">seleniumbase/SeleniumBase#4472</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.52.2...v4.52.3">https://github.com/seleniumbase/SeleniumBase/compare/v4.52.2...v4.52.3</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/9112244cfada5d002f3d08c6dbf2a68d34598c51"><code>9112244</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4472">#4472</a>
from seleniumbase/mcp-server-patch-1</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/bf1abf63240338b9ee58f4dc6e907e627411c27b"><code>bf1abf6</code></a>
Version 4.52.3</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/405c7c68599108fc99d1cf929e01b17d2c62cd7a"><code>405c7c6</code></a>
Update examples</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/213580314cb106bcb14d857289eb494395f491ae"><code>2135803</code></a>
Refresh Python dependencies</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/1584e5b1b83f7177c59810817942150d1ed3ecab"><code>1584e5b</code></a>
Update the docs for MCP servers</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/0e14a09f2d2f2e62a85bacc890b1d9d48b9a0c79"><code>0e14a09</code></a>
Update logging messages</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/9bdc1133d096562111d3cc6465c6b0cd5dbfc38d"><code>9bdc113</code></a>
Fix the MCP Server on Python versions less than 3.14</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/cbd624a8697c763d7d68f3e92dcd31f4636ae9d8"><code>cbd624a</code></a>
Update the docs</li>
<li>See full diff in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.52.2...v4.52.3">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-30 12:18:11 -04:00
dependabot[bot] faaa119884 build(deps): bump python from ce40764 to cae66f2 (#1278)
Bumps python from `ce40764` to `cae66f2`.


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=python&package-manager=docker&previous-version=3.14.7-slim&new-version=3.14.7-slim)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-30 12:18:03 -04:00
dependabot[bot] be41a92436 build(deps-dev): bump the npm-deps group in /src/frontend with 7 updates (#1279)
Bumps the npm-deps group in /src/frontend with 7 updates:

| Package | From | To |
| --- | --- | --- |
|
[@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)
| `26.2.0` | `26.3.0` |
|
[@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom)
| `19.2.4` | `19.2.5` |
|
[@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react)
| `6.0.5` | `6.1.0` |
| [oxfmt](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt) |
`0.63.0` | `0.65.0` |
| [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) |
`1.78.0` | `1.80.0` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) |
`8.2.1` | `8.2.2` |
|
[vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)
| `4.1.10` | `4.1.11` |

Updates `@types/node` from 26.2.0 to 26.3.0
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node">compare
view</a></li>
</ul>
</details>
<br />

Updates `@types/react-dom` from 19.2.4 to 19.2.5
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom">compare
view</a></li>
</ul>
</details>
<br />

Updates `@vitejs/plugin-react` from 6.0.5 to 6.1.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite-plugin-react/releases">@​vitejs/plugin-react's
releases</a>.</em></p>
<blockquote>
<h2>plugin-react@6.1.0</h2>
<h3>Add experimental native React Compiler support (<a
href="https://redirect.github.com/vitejs/vite-plugin-react/pull/1419">#1419</a>)</h3>
<p>Add experimental native React Compiler support.</p>
<p>You can use it by installing <code>oxc-transform-react</code> and
enabling it via the <code>compiler</code> option:</p>
<pre lang="sh"><code>npm install -D oxc-transform-react
</code></pre>
<pre lang="js"><code>import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
<p>export default defineConfig({<br />
plugins: [<br />
react({ compiler: true })<br />
]<br />
})<br />
</code></pre></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md">@​vitejs/plugin-react's
changelog</a>.</em></p>
<blockquote>
<h2>6.1.0 (2026-08-19)</h2>
<h3>Add experimental native React Compiler support (<a
href="https://redirect.github.com/vitejs/vite-plugin-react/pull/1419">#1419</a>)</h3>
<p>Add experimental native React Compiler support.</p>
<p>You can use it by installing <code>oxc-transform-react</code> and
enabling it via the <code>compiler</code> option:</p>
<pre lang="sh"><code>npm install -D oxc-transform-react
</code></pre>
<pre lang="js"><code>import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
<p>export default defineConfig({<br />
plugins: [<br />
react({ compiler: true })<br />
]<br />
})<br />
</code></pre></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/39b31735bf79c2dd380eedaba7ed849256f92a29"><code>39b3173</code></a>
release: plugin-react@6.1.0 (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1428">#1428</a>)</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/f1340b0c760b1c16e1b780eeba46fd933ddd52eb"><code>f1340b0</code></a>
feat(react): add native React Compiler support (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1419">#1419</a>)</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/9ab698eafc38ffa14861db450291ed2f6f557557"><code>9ab698e</code></a>
fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1375">#1375</a>)</li>
<li>See full diff in <a
href="https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.1.0/packages/plugin-react">compare
view</a></li>
</ul>
</details>
<br />

Updates `oxfmt` from 0.63.0 to 0.65.0
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/commit/97e99b85483776a72928d675cc05b1cfc1130ba0"><code>97e99b8</code></a>
release(apps): oxlint v1.80.0 &amp;&amp; oxfmt v0.65.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/26045">#26045</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/0db127cc16d28b97d84bac4ebeb302caf1a78c7e"><code>0db127c</code></a>
release(apps): oxlint v1.79.0 &amp;&amp; oxfmt v0.64.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/25866">#25866</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/c07fe7c217774fd404740d34ee91ac03a6b726c2"><code>c07fe7c</code></a>
feat(oxfmt): support <code>experimentalOperatorPosition</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/25643">#25643</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/fed6681edaf3b9b45fbcc8fd7f987c86505d0b86"><code>fed6681</code></a>
docs(oxfmt): skip expanding overrides options (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/25572">#25572</a>)</li>
<li>See full diff in <a
href="https://github.com/oxc-project/oxc/commits/oxfmt_v0.65.0/npm/oxfmt">compare
view</a></li>
</ul>
</details>
<br />

Updates `oxlint` from 1.78.0 to 1.80.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/releases">oxlint's
releases</a>.</em></p>
<blockquote>
<h2>oxlint v1.80.0 &amp; oxfmt v0.65.0</h2>
<h2>Table of Contents</h2>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/blob/HEAD/#oxlint-v1.80.0">Oxlint
v1.80.0</a></li>
<li><a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/blob/HEAD/#oxfmt-v0.65.0">Oxfmt
v0.65.0</a></li>
</ul>
<h2>Oxlint v1.80.0</h2>
<h3>🚀 Features</h3>
<ul>
<li>70c3e35 linter/typescript/no-confusing-non-null-assertion: Implement
suggestion (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26012">#26012</a>)
(Mikhail Baev)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>17ae11c linter/oxc/double-comparisons: Handle grouped logical
expressions (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26044">#26044</a>)
(camc314)</li>
<li>8a353a7 linter/eslint/no-control-regex: Refine help message text (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25996">#25996</a>)
(Rahul Mishra)</li>
<li>8a9bdbd estree: Include decorators in <code>FormalParameterRest
</code> spans (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26021">#26021</a>)
(camc314)</li>
<li>8d94cd1 linter/eslint/no-useless-rename: Preserve type modifiers (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26020">#26020</a>)
(Cameron)</li>
<li>2cde1f6 rust: Address nightly deprecations (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25998">#25998</a>)
(Boshen)</li>
<li>51d36d7 linter/vue: Resolve <code>vue</code> imports via shared
import helpers (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25903">#25903</a>)
(Connor Shea)</li>
<li>83a68d2 linter/react/no-react-children: Resolve <code>react</code>
imports by symbol (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25901">#25901</a>)
(Connor Shea)</li>
<li>124e196 linter: Resolve globals by reference, not by name (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25905">#25905</a>)
(Connor Shea)</li>
<li>a701bcc linter: Remove invalid React compiler doc links (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25900">#25900</a>)
(Boshen)</li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>9b7e153 linter: Set <code>version</code> to 1.79.0 for rules shipped
in 1.79.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25902">#25902</a>)
(connorshea)</li>
</ul>
<h2>Oxfmt v0.65.0</h2>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>bf37dd5 formatter: Preserve class decorators before export when the
statement is suppressed (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26034">#26034</a>)
(leaysgur)</li>
</ul>
<h2>oxlint v1.79.0 &amp; oxfmt v0.64.0</h2>
<h2>Table of Contents</h2>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/blob/HEAD/#oxlint-v1.79.0">Oxlint
v1.79.0</a></li>
<li><a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/blob/HEAD/#oxfmt-v0.64.0">Oxfmt
v0.64.0</a></li>
</ul>
<h2>Oxlint v1.79.0</h2>
<h3>💥 BREAKING CHANGES</h3>
<ul>
<li>8c4552d linter: [<strong>BREAKING</strong>] Split
react/react-compiler into per-category rules (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25500">#25500</a>)
(Boshen)</li>
</ul>
<p>See <a
href="https://oxc.rs/blog/2026-08-18-react-compiler-support">React
Compiler Support</a> for details.</p>
<h3>🚀 Features</h3>
<ul>
<li>9b7394e linter/typescript/no-empty-object-type: Implement suggestion
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25833">#25833</a>)
(Mikhail Baev)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md">oxlint's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<p>All notable changes to this package will be documented in this
file.</p>
<p>The format is based on <a
href="https://keepachangelog.com/en/1.0.0">Keep a Changelog</a>.</p>
<h2>[1.79.0] - 2026-08-18</h2>
<h3>💥 BREAKING CHANGES</h3>
<ul>
<li>8c4552d linter: [<strong>BREAKING</strong>] Split
react/react-compiler into per-category rules (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25500">#25500</a>)
(Boshen)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>228e8e0 linter: Resolve inactive React compiler rules (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25830">#25830</a>)
(Boshen)</li>
<li>aa49d86 linter: Allow spread rule options in config types (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25675">#25675</a>)
(ch3rry)</li>
<li>36f8451 linter/eslint/no-eval: Align indirect default with ESLint
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25656">#25656</a>)
(camc314)</li>
<li>beb724d linter/eslint/no-unused-vars: Report bare underscore
parameters (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25663">#25663</a>)
(camc314)</li>
<li>4004c10 linter/eslint/no-irregular-whitespace: Check comments by
default (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25660">#25660</a>)
(camc314)</li>
<li>285820e linter/no-large-snapshots: Precompile and document allowed
snapshot matchers (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25611">#25611</a>)
(Mikhail Baev)</li>
<li>4df5835 linter: Allow capitalized built-in calls (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25516">#25516</a>)
(Boshen)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/commit/97e99b85483776a72928d675cc05b1cfc1130ba0"><code>97e99b8</code></a>
release(apps): oxlint v1.80.0 &amp;&amp; oxfmt v0.65.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/26045">#26045</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/0db127cc16d28b97d84bac4ebeb302caf1a78c7e"><code>0db127c</code></a>
release(apps): oxlint v1.79.0 &amp;&amp; oxfmt v0.64.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25866">#25866</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/228e8e0f85c0e7aeded02c5e27fd810004d3b41a"><code>228e8e0</code></a>
fix(linter): resolve inactive React compiler rules (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25830">#25830</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/aa49d860465e6c00b6edfcbb8973d8dc95cc11ca"><code>aa49d86</code></a>
fix(linter): allow spread rule options in config types (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25675">#25675</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/892238149b7c4dff808817ec5e27d1e0ecf63b11"><code>8922381</code></a>
refactor(linter): remove inactive react config rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25740">#25740</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/8c4552dfa6bce0a9f06f41ca13e45e50d842c38c"><code>8c4552d</code></a>
feat(linter)!: split react/react-compiler into per-category rules (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25500">#25500</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/36f845168ce854c1c970fea13997e16a18cbe55f"><code>36f8451</code></a>
fix(linter/eslint/no-eval): align indirect default with ESLint (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25656">#25656</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/beb724dce2e8b8466d851c04e16c38fa75623c5c"><code>beb724d</code></a>
fix(linter/eslint/no-unused-vars): report bare underscore parameters (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25663">#25663</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/4004c101ca349f8e92932f6d056b18bfb4dff9a7"><code>4004c10</code></a>
fix(linter/eslint/no-irregular-whitespace): check comments by default
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25660">#25660</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/285820eed6c49a45f8de18d3bfed1cc6b5d9da6d"><code>285820e</code></a>
fix(linter/no-large-snapshots): precompile and document allowed snapshot
matc...</li>
<li>Additional commits viewable in <a
href="https://github.com/oxc-project/oxc/commits/oxlint_v1.80.0/npm/oxlint">compare
view</a></li>
</ul>
</details>
<br />

Updates `vite` from 8.2.1 to 8.2.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/releases">vite's
releases</a>.</em></p>
<blockquote>
<h2>plugin-legacy@8.2.2</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/plugin-legacy@8.2.2/packages/plugin-legacy/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v8.2.2</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v8.2.2/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md">vite's
changelog</a>.</em></p>
<blockquote>
<h2><!-- raw HTML omitted --><a
href="https://github.com/vitejs/vite/compare/v8.2.1...v8.2.2">8.2.2</a>
(2026-08-20)<!-- raw HTML omitted --></h2>
<h3>Features</h3>
<ul>
<li><strong>deps:</strong> widen <code>@vitejs/devtools</code> peer
range to v0.5.0 (<a
href="https://redirect.github.com/vitejs/vite/issues/23302">#23302</a>)
(<a
href="https://github.com/vitejs/vite/commit/495d9ff5a7d843ca876a9e49799947a5deb704c7">495d9ff</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>bundled-dev:</strong> handle lazy request error (<a
href="https://redirect.github.com/vitejs/vite/issues/23291">#23291</a>)
(<a
href="https://github.com/vitejs/vite/commit/3ba026dade4af56df08815310d3458fa110f5c5c">3ba026d</a>)</li>
<li><strong>bundled-dev:</strong> hot update through circular imports
instead of reloading (<a
href="https://redirect.github.com/vitejs/vite/issues/23259">#23259</a>)
(<a
href="https://github.com/vitejs/vite/commit/3dbddefaafc091a879b06f9279296f776691e455">3dbddef</a>)</li>
<li><strong>config:</strong> resolve sourcemap paths against sourcemap
location (<a
href="https://redirect.github.com/vitejs/vite/issues/23239">#23239</a>)
(<a
href="https://github.com/vitejs/vite/commit/05a003e6a17a84d75f907ea0f1598bc39b8dce6c">05a003e</a>)</li>
<li><strong>css:</strong> don't pass empty targets to lightningcss (<a
href="https://redirect.github.com/vitejs/vite/issues/23295">#23295</a>)
(<a
href="https://github.com/vitejs/vite/commit/2804636ff608d105928009d274ffba7cfbe55340">2804636</a>)</li>
<li><strong>define:</strong> fix match escaped dots to support
$-prefixed define keys (<a
href="https://redirect.github.com/vitejs/vite/issues/23249">#23249</a>)
(<a
href="https://github.com/vitejs/vite/commit/dcf88bd2ad2b1a8845f9029587cc8c825e382d42">dcf88bd</a>)</li>
<li><strong>deps:</strong> update all non-major dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/23217">#23217</a>)
(<a
href="https://github.com/vitejs/vite/commit/ba958bddfc9cabe302c6b34269dcf5c9634531e0">ba958bd</a>)</li>
<li><strong>deps:</strong> update rolldown-related dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/23218">#23218</a>)
(<a
href="https://github.com/vitejs/vite/commit/83ecb2c8059e8ce946a7cc835d4c14ef78aef4fd">83ecb2c</a>)</li>
<li><strong>module-runner:</strong> exclude completed modules from
in-flight cycle detection (fix <a
href="https://redirect.github.com/vitejs/vite/issues/22999">#22999</a>)
(<a
href="https://redirect.github.com/vitejs/vite/issues/23009">#23009</a>)
(<a
href="https://github.com/vitejs/vite/commit/d9b10a98db1c293ee64300bd75d568b44c8ae931">d9b10a9</a>)</li>
<li><strong>optimizer:</strong> close custom extension analysis bundles
(<a
href="https://redirect.github.com/vitejs/vite/issues/23207">#23207</a>)
(<a
href="https://github.com/vitejs/vite/commit/8fb76752836f61224d3095b502fa237b478a06b2">8fb7675</a>)</li>
<li>reduce Windows 8.3-short-name detection false-positives (<a
href="https://redirect.github.com/vitejs/vite/issues/23066">#23066</a>)
(<a
href="https://github.com/vitejs/vite/commit/02cffa9e2d38d5d8f12e4043ee9d0f7abb1471e2">02cffa9</a>)</li>
<li>respect <code>resolve.preserveSymlinks</code> when resolving root
(fix <a
href="https://redirect.github.com/vitejs/vite/issues/23197">#23197</a>)
(<a
href="https://redirect.github.com/vitejs/vite/issues/23198">#23198</a>)
(<a
href="https://github.com/vitejs/vite/commit/8413052731836d4aaf3eb94a0f25788dd35d2888">8413052</a>)</li>
<li><strong>ssr:</strong> rewrite computed key of destructing parameter
(<a
href="https://redirect.github.com/vitejs/vite/issues/23307">#23307</a>)
(<a
href="https://github.com/vitejs/vite/commit/9db0b61d4c9c7caad7ea1d9670b637faf2bb6c93">9db0b61</a>)</li>
<li><strong>vite:</strong> update outdated upstream file links in
license comments (<a
href="https://redirect.github.com/vitejs/vite/issues/23285">#23285</a>)
(<a
href="https://github.com/vitejs/vite/commit/c0f2fc607ee97ee4499337b04826420c00654065">c0f2fc6</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li><strong>build:</strong> note cssTarget precedence (<a
href="https://redirect.github.com/vitejs/vite/issues/23200">#23200</a>)
(<a
href="https://github.com/vitejs/vite/commit/a20a35ec0685e374519864d0f41dd5f6e9ba0271">a20a35e</a>)</li>
</ul>
<h3>Miscellaneous Chores</h3>
<ul>
<li>fix ts errors in build test cases (<a
href="https://redirect.github.com/vitejs/vite/issues/23209">#23209</a>)
(<a
href="https://github.com/vitejs/vite/commit/a0cfcf72f8ef8bf0f2f11d553333b9bb31f1d316">a0cfcf7</a>)</li>
</ul>
<h3>Code Refactoring</h3>
<ul>
<li>use JSON import attributes instead of readFileSync in constants (<a
href="https://redirect.github.com/vitejs/vite/issues/23258">#23258</a>)
(<a
href="https://github.com/vitejs/vite/commit/1d9fa392a43229241f80630236f8552ce8f7cd0f">1d9fa39</a>)</li>
<li>use named regex constants over inline literals (<a
href="https://redirect.github.com/vitejs/vite/issues/22964">#22964</a>)
(<a
href="https://github.com/vitejs/vite/commit/5c1c6c609718303202832f706884192e1f1e9223">5c1c6c6</a>)</li>
</ul>
<h3>Tests</h3>
<ul>
<li><strong>define:</strong> close rolldown bundler after generate (<a
href="https://redirect.github.com/vitejs/vite/issues/23231">#23231</a>)
(<a
href="https://github.com/vitejs/vite/commit/b4d66fee14d970f45b8a6f3d7d6aee73ca9b88ab">b4d66fe</a>)</li>
<li><strong>module-runner:</strong> add TLA circular import case (<a
href="https://redirect.github.com/vitejs/vite/issues/23299">#23299</a>)
(<a
href="https://github.com/vitejs/vite/commit/4a261f242831bef92afd2f1aacfb81eab9dec371">4a261f2</a>)</li>
<li><strong>module-runner:</strong> simplify server-hmr tests (<a
href="https://redirect.github.com/vitejs/vite/issues/23300">#23300</a>)
(<a
href="https://github.com/vitejs/vite/commit/599b44b6600ec426e10cd556908d53b027b0c4fb">599b44b</a>)</li>
<li><strong>ssr:</strong> add destructing assignment case for
moduleRunnerTransform (<a
href="https://redirect.github.com/vitejs/vite/issues/23308">#23308</a>)
(<a
href="https://github.com/vitejs/vite/commit/cb77e2a93bad2a8ece00b4aa0ef507c092582c45">cb77e2a</a>)</li>
</ul>
<h3>Build System</h3>
<ul>
<li>use JSON import attributes instead of readFIleSync in rolldown
configs (<a
href="https://redirect.github.com/vitejs/vite/issues/23251">#23251</a>)
(<a
href="https://github.com/vitejs/vite/commit/d615bcdb23d96c1ca5ce1ee45e21d8d87381106f">d615bcd</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/vitejs/vite/commit/de1111ab0be00879b404e7ed3b2a80e264edddc1"><code>de1111a</code></a>
release: v8.2.2</li>
<li><a
href="https://github.com/vitejs/vite/commit/cb77e2a93bad2a8ece00b4aa0ef507c092582c45"><code>cb77e2a</code></a>
test(ssr): add destructing assignment case for moduleRunnerTransform (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23308">#23308</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/9db0b61d4c9c7caad7ea1d9670b637faf2bb6c93"><code>9db0b61</code></a>
fix(ssr): rewrite computed key of destructing parameter (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23307">#23307</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/8413052731836d4aaf3eb94a0f25788dd35d2888"><code>8413052</code></a>
fix: respect <code>resolve.preserveSymlinks</code> when resolving root
(fix <a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23197">#23197</a>)
(<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23">#23</a>...</li>
<li><a
href="https://github.com/vitejs/vite/commit/05a003e6a17a84d75f907ea0f1598bc39b8dce6c"><code>05a003e</code></a>
fix(config): resolve sourcemap paths against sourcemap location (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23239">#23239</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/495d9ff5a7d843ca876a9e49799947a5deb704c7"><code>495d9ff</code></a>
feat(deps): widen <code>@vitejs/devtools</code> peer range to v0.5.0 (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23302">#23302</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/1d9fa392a43229241f80630236f8552ce8f7cd0f"><code>1d9fa39</code></a>
refactor: use JSON import attributes instead of readFileSync in
constants (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/2">#2</a>...</li>
<li><a
href="https://github.com/vitejs/vite/commit/2804636ff608d105928009d274ffba7cfbe55340"><code>2804636</code></a>
fix(css): don't pass empty targets to lightningcss (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23295">#23295</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/599b44b6600ec426e10cd556908d53b027b0c4fb"><code>599b44b</code></a>
test(module-runner): simplify server-hmr tests (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23300">#23300</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/4a261f242831bef92afd2f1aacfb81eab9dec371"><code>4a261f2</code></a>
test(module-runner): add TLA circular import case (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23299">#23299</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/vitejs/vite/commits/v8.2.2/packages/vite">compare
view</a></li>
</ul>
</details>
<br />

Updates `vitest` from 4.1.10 to 4.1.11
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitest-dev/vitest/releases">vitest's
releases</a>.</em></p>
<blockquote>
<h2>v4.1.11</h2>
<h3>   🐞 Bug Fixes</h3>
<ul>
<li>Revive global concurrency limit for test lifecycle [backport to v4]
 -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> and
<a href="https://github.com/hi-ogawa"><code>@​hi-ogawa</code></a> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10992">vitest-dev/vitest#10992</a>
<a href="https://github.com/vitest-dev/vitest/commit/5146df80b"><!-- raw
HTML omitted -->(5146d)<!-- raw HTML omitted --></a></li>
<li><strong>browser</strong>:
<ul>
<li>Encode iframeId in tester iframe URL [backport to v4]  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a>,
<strong>Pduhard</strong> and <strong>Claude Opus 4.8</strong> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10955">vitest-dev/vitest#10955</a>
<a href="https://github.com/vitest-dev/vitest/commit/10b2cd201"><!-- raw
HTML omitted -->(10b2c)<!-- raw HTML omitted --></a></li>
<li>Trigger playwright/chromium gc on lower disk availability [backport
to v4]  -  by <a
href="https://github.com/hi-ogawa"><code>@​hi-ogawa</code></a>,
<strong>Hiroshi Ogawa</strong> and <strong>OpenCode</strong> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10951">vitest-dev/vitest#10951</a>
<a href="https://github.com/vitest-dev/vitest/commit/9851dbc41"><!-- raw
HTML omitted -->(9851d)<!-- raw HTML omitted --></a></li>
</ul>
</li>
<li><strong>mocker</strong>:
<ul>
<li>Restrict redirect mocks to the fs allowlist [backport to v4]  -  by
<a href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a>
in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10974">vitest-dev/vitest#10974</a>
<a href="https://github.com/vitest-dev/vitest/commit/fe5a11d3c"><!-- raw
HTML omitted -->(fe5a1)<!-- raw HTML omitted --></a></li>
</ul>
</li>
</ul>
<h5>    <a
href="https://github.com/vitest-dev/vitest/compare/v4.1.10...v4.1.11">View
changes on GitHub</a></h5>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/vitest-dev/vitest/commit/9bd8d464e6328c567c2dbcd8fdd977d57a9425c2"><code>9bd8d46</code></a>
chore: release v4.1.11 (<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/10995">#10995</a>)</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/9851dbc41c286a30abfb6b29cce65f3e5b7b40a1"><code>9851dbc</code></a>
fix(browser): trigger playwright/chromium gc on lower disk availability
[back...</li>
<li>See full diff in <a
href="https://github.com/vitest-dev/vitest/commits/v4.1.11/packages/vitest">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-30 12:17:56 -04:00
dependabot[bot] 7de9319c7a build(deps): bump the gh-actions group with 3 updates (#1280)
Bumps the gh-actions group with 3 updates:
[github/codeql-action/init](https://github.com/github/codeql-action),
[github/codeql-action/autobuild](https://github.com/github/codeql-action)
and
[github/codeql-action/analyze](https://github.com/github/codeql-action).

Updates `github/codeql-action/init` from 4.37.7 to 4.37.8
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/init's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.8</h2>
<p>No user facing changes.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/init's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.9 - 26 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.4">2.26.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4106">#4106</a></li>
</ul>
<h2>4.37.8 - 21 Aug 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.7 - 13 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28"><code>db488dd</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4102">#4102</a>
from github/update-v4.37.8-9ee088e13</li>
<li><a
href="https://github.com/github/codeql-action/commit/1845f5ba8b4057590f49ee8e246c95ef2ba4b53f"><code>1845f5b</code></a>
Update changelog for v4.37.8</li>
<li><a
href="https://github.com/github/codeql-action/commit/9ee088e13615f8d1eaef4766f9dde95d3356a8f6"><code>9ee088e</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4080">#4080</a>
from github/henrymercer/studious-giggle</li>
<li><a
href="https://github.com/github/codeql-action/commit/1aef003397c876c0ab5bd118e1b1f34c175622e9"><code>1aef003</code></a>
Address review feedback on overlay disk flags</li>
<li><a
href="https://github.com/github/codeql-action/commit/508b83bc415e8df76ce8ea08c0cf42c2529ebc63"><code>508b83b</code></a>
Merge main into overlay minimum disk feature branch</li>
<li><a
href="https://github.com/github/codeql-action/commit/d97b3428e8eebbb1810cf454d6397886d136b4ba"><code>d97b342</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4098">#4098</a>
from github/mbg/permission-error-as-configuration-error</li>
<li><a
href="https://github.com/github/codeql-action/commit/47fa6222231b12097f83215dd7a6b4a0915841fd"><code>47fa622</code></a>
Make <code>EACCES</code> a <code>ConfigurationError</code></li>
<li><a
href="https://github.com/github/codeql-action/commit/45693cc6882bb175b58a06818c91876e201037c7"><code>45693cc</code></a>
Refactor <code>ENOSPC</code> check into
<code>isDiskConfigurationError</code> function</li>
<li><a
href="https://github.com/github/codeql-action/commit/c2fd8f54d19fa46c94ed79cb92e6dd6606d61762"><code>c2fd8f5</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4081">#4081</a>
from github/mario-campos/version-cache-to-disk</li>
<li><a
href="https://github.com/github/codeql-action/commit/c56f48e9bd458a387eb68a68534459e503e56b17"><code>c56f48e</code></a>
Log unexpected conditions during caching CLI output</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd...db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/autobuild` from 4.37.7 to 4.37.8
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/autobuild's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.8</h2>
<p>No user facing changes.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/autobuild's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.9 - 26 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.4">2.26.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4106">#4106</a></li>
</ul>
<h2>4.37.8 - 21 Aug 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.7 - 13 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28"><code>db488dd</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4102">#4102</a>
from github/update-v4.37.8-9ee088e13</li>
<li><a
href="https://github.com/github/codeql-action/commit/1845f5ba8b4057590f49ee8e246c95ef2ba4b53f"><code>1845f5b</code></a>
Update changelog for v4.37.8</li>
<li><a
href="https://github.com/github/codeql-action/commit/9ee088e13615f8d1eaef4766f9dde95d3356a8f6"><code>9ee088e</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4080">#4080</a>
from github/henrymercer/studious-giggle</li>
<li><a
href="https://github.com/github/codeql-action/commit/1aef003397c876c0ab5bd118e1b1f34c175622e9"><code>1aef003</code></a>
Address review feedback on overlay disk flags</li>
<li><a
href="https://github.com/github/codeql-action/commit/508b83bc415e8df76ce8ea08c0cf42c2529ebc63"><code>508b83b</code></a>
Merge main into overlay minimum disk feature branch</li>
<li><a
href="https://github.com/github/codeql-action/commit/d97b3428e8eebbb1810cf454d6397886d136b4ba"><code>d97b342</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4098">#4098</a>
from github/mbg/permission-error-as-configuration-error</li>
<li><a
href="https://github.com/github/codeql-action/commit/47fa6222231b12097f83215dd7a6b4a0915841fd"><code>47fa622</code></a>
Make <code>EACCES</code> a <code>ConfigurationError</code></li>
<li><a
href="https://github.com/github/codeql-action/commit/45693cc6882bb175b58a06818c91876e201037c7"><code>45693cc</code></a>
Refactor <code>ENOSPC</code> check into
<code>isDiskConfigurationError</code> function</li>
<li><a
href="https://github.com/github/codeql-action/commit/c2fd8f54d19fa46c94ed79cb92e6dd6606d61762"><code>c2fd8f5</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4081">#4081</a>
from github/mario-campos/version-cache-to-disk</li>
<li><a
href="https://github.com/github/codeql-action/commit/c56f48e9bd458a387eb68a68534459e503e56b17"><code>c56f48e</code></a>
Log unexpected conditions during caching CLI output</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd...db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/analyze` from 4.37.7 to 4.37.8
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/analyze's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.8</h2>
<p>No user facing changes.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/analyze's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.9 - 26 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.4">2.26.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4106">#4106</a></li>
</ul>
<h2>4.37.8 - 21 Aug 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.7 - 13 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28"><code>db488dd</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4102">#4102</a>
from github/update-v4.37.8-9ee088e13</li>
<li><a
href="https://github.com/github/codeql-action/commit/1845f5ba8b4057590f49ee8e246c95ef2ba4b53f"><code>1845f5b</code></a>
Update changelog for v4.37.8</li>
<li><a
href="https://github.com/github/codeql-action/commit/9ee088e13615f8d1eaef4766f9dde95d3356a8f6"><code>9ee088e</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4080">#4080</a>
from github/henrymercer/studious-giggle</li>
<li><a
href="https://github.com/github/codeql-action/commit/1aef003397c876c0ab5bd118e1b1f34c175622e9"><code>1aef003</code></a>
Address review feedback on overlay disk flags</li>
<li><a
href="https://github.com/github/codeql-action/commit/508b83bc415e8df76ce8ea08c0cf42c2529ebc63"><code>508b83b</code></a>
Merge main into overlay minimum disk feature branch</li>
<li><a
href="https://github.com/github/codeql-action/commit/d97b3428e8eebbb1810cf454d6397886d136b4ba"><code>d97b342</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4098">#4098</a>
from github/mbg/permission-error-as-configuration-error</li>
<li><a
href="https://github.com/github/codeql-action/commit/47fa6222231b12097f83215dd7a6b4a0915841fd"><code>47fa622</code></a>
Make <code>EACCES</code> a <code>ConfigurationError</code></li>
<li><a
href="https://github.com/github/codeql-action/commit/45693cc6882bb175b58a06818c91876e201037c7"><code>45693cc</code></a>
Refactor <code>ENOSPC</code> check into
<code>isDiskConfigurationError</code> function</li>
<li><a
href="https://github.com/github/codeql-action/commit/c2fd8f54d19fa46c94ed79cb92e6dd6606d61762"><code>c2fd8f5</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4081">#4081</a>
from github/mario-campos/version-cache-to-disk</li>
<li><a
href="https://github.com/github/codeql-action/commit/c56f48e9bd458a387eb68a68534459e503e56b17"><code>c56f48e</code></a>
Log unexpected conditions during caching CLI output</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd...db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-30 12:17:50 -04:00
CaliBrain 97e289ae13 fix: search, Prowlarr and qBittorrent follow-ups (#1276, #1283) (#1284) 2026-08-30 03:09:13 -04:00
Jorge Lima c95ee72ad5 fix(qbittorrent): keep magnets whose metadata is still pending (#1282)
## Problem

`QBittorrentClient.add_download()` waits 20 × 0.5 s for qBittorrent to
leave `metaDL`, then raises:

```
Failed to add to qbittorrent: Torrent metadata resolution was not confirmed within the visibility grace period
(response=TorrentsAddedMetadata({'added_torrent_ids': [], 'failure_count': 0, 'pending_count': 1, 'success_count': 0}))
```

The wait exists to learn qBittorrent's primary torrent ID, which for
hybrid torrents switches from the v1 hash to the truncated v2 hash once
metadata resolves. A magnet on a thin public swarm routinely needs
longer than 10 s to find a peer that will serve metadata, and the
download is then abandoned even though the add itself succeeded. The
torrent stays in qBittorrent (`base_handler` logs "leaving in
qbittorrent") and often completes minutes later with nobody watching it.

Seen on v1.3.12 with public indexers through Prowlarr: every magnet-only
release failed this way, while `.torrent` releases from a private
indexer were fine. qBittorrent showed the same torrents at `metaDL 0%
seeds=0/0`, and they resolved on their own well after shelfmark had
given up.

## Change

Return the info hash we already have instead of raising when the grace
period expires. Reads then resolve either identity:

- `get_status()` and `get_download_path()` use `_resolve_torrent()`
instead of `_get_torrent_info()`, so a v1 hash still matches after
qBittorrent re-keys the torrent to v2. `_torrent_matches_download_id`
already compares `hash`, `infohash_v1` and `infohash_v2`.
- `remove()` and `set_category()` address the torrent by its current
primary hash through a new `_current_hash()` helper, which falls back to
the ID it was given when the torrent cannot be resolved.
- The two magic numbers become `_METADATA_WAIT_POLLS` and
`_METADATA_WAIT_INTERVAL_SECONDS`.

The happy path does not change. When metadata resolves inside the grace
period the resolved primary hash comes back as before, and
`_resolve_torrent()` tries the exact-hash lookup first, so it costs no
extra request.

## Tests

`test_add_fails_when_metadata_never_resolves` asserted the old
behaviour, so it becomes
`test_add_keeps_torrent_when_metadata_never_resolves` and asserts the
info hash is returned.
`test_get_status_resolves_hash_after_metadata_switch` is new: it reads
status by the v1 hash after qBittorrent reports the torrent under its v2
hash.

`uv run pytest tests/ --ignore=tests/e2e` gives the same 55 failures
with and without this change (they are all in `tests/bypass/` and need
Chrome, which my machine has no headless setup for), and
`tests/prowlarr/` is green at 524 passed. Ruff check and format are
clean. I have not run this branch against a live qBittorrent, so a
second pair of eyes on the `remove()` path would help.
2026-08-30 02:00:42 -04:00
CaliBrain b25acdb2ad fix(packs): don't disrupt normal downloads when inspecting for packs (#1274)
Follow-ups to the multi-book pack feature (#1270), which inspects every
release before download. Two behaviours leaked into the ordinary
single-book
flow and are corrected here:

- A flat folder of chaptered audio (`01 - Chapter.mp3`, `02 - ...`) was
detected as a pack, because each track name parses to a series position,
so
clicking download popped the review panel for one normal audiobook. Flat
folders are now split one-book-per-file only with real evidence of
distinct
books: two or more series positions, more than one title, and no
chaptered
audio (only the single-file m4b/m4a containers and ebook formats
qualify).
  Subfolder packs and flat m4b/m4a packs are unchanged.

- Every release that couldn't be inspected (usenet, magnet-only, sources
  without a list_files hook, ABB single-file) showed an info toast on
download. That is now a console.warn, so a normal download is silent
again.

Adds regression tests for the chaptered-mp3 cases.
2026-08-27 01:00:54 -04:00
dependabot[bot] 7569aaecc5 build(deps): bump seleniumbase from 4.52.1 to 4.52.2 in the python-deps group (#1273)
Bumps the python-deps group with 1 update:
[seleniumbase](https://github.com/seleniumbase/SeleniumBase).

Updates `seleniumbase` from 4.52.1 to 4.52.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.52.2 - MCP Server Support</h2>
<h2>MCP Server Support</h2>
<p><strong>If you love AI tools, this is one of the biggest releases
this year for SeleniumBase!</strong>
<strong>The new <code>seleniumbase-mcp</code> command starts the
&quot;Pure CDP Mode&quot; MCP Server.</strong>
<strong>(Be sure to install <code>seleniumbase[mcp]</code> to get
<code>mcp&gt;=2.0.0</code>!)</strong>
<strong>(To debug the MCP server from a <code>git clone</code> of
SeleniumBase, get <code>uv</code> as well before calling <code>mcp dev
server.py</code> from the <code>SeleniumBase/mcp_servers/</code>
folder.)</strong></p>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/4014476e288d004a8de38eca582631c9c653fa22">Add
a SeleniumBase MCP server</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/ac7e449fbbc8a6e9347e65e4ced8176ba1655112">Add
.mcp.json to the root folder</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/32fac6becaabbb288be42e98d6528108bdb05e73">Update
setup.cfg files</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/168500f45b547c914539057f76e3cd31e0121461">Update
.gitignore</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/bb495cb23e405ab9a523068cdb64b48f487f22c5">Update
.dockerignore</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/f030eae7b492f41d1c74c731e775f21b853afbdf">Add
configuration for using the new MCP server</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/422bb2fc11bd6ef58758c0eff1456f033a94db1f">Refresh
Python dependencies</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/7b245c033dc47e356fdaa99b5db7ebf965329bfe">Update
ReadMe files</a></li>
</ul>
<p><strong>Note that you will need <code>mcp&gt;=2.0.0</code> for the
MCP Server to work!</strong></p>
<p>⚠️ Note: Due to a typing bug, (<a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4471">seleniumbase/SeleniumBase#4471</a>),
the MCP Server in this release only worked on Python 3.14+.
Upgrade to <a
href="https://github.com/seleniumbase/SeleniumBase/releases/tag/v4.52.3">https://github.com/seleniumbase/SeleniumBase/releases/tag/v4.52.3</a>
for the fix.</p>
<h2>What's Changed</h2>
<ul>
<li>MCP Server Support by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4470">seleniumbase/SeleniumBase#4470</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.52.1...v4.52.2">https://github.com/seleniumbase/SeleniumBase/compare/v4.52.1...v4.52.2</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/a28aa518e34c859d3a3f90daf03c1aa926940931"><code>a28aa51</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4470">#4470</a>
from seleniumbase/mcp-server-support</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/7b245c033dc47e356fdaa99b5db7ebf965329bfe"><code>7b245c0</code></a>
Update ReadMe files</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/1e5ac8dd110ecb96add400a45f009735be5e8683"><code>1e5ac8d</code></a>
Version 4.52.2</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/422bb2fc11bd6ef58758c0eff1456f033a94db1f"><code>422bb2f</code></a>
Refresh Python dependencies</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/f030eae7b492f41d1c74c731e775f21b853afbdf"><code>f030eae</code></a>
Add configuration for using the new MCP server</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/bb495cb23e405ab9a523068cdb64b48f487f22c5"><code>bb495cb</code></a>
Update <code>.dockerignore</code></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/168500f45b547c914539057f76e3cd31e0121461"><code>168500f</code></a>
Update <code>.gitignore</code></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/32fac6becaabbb288be42e98d6528108bdb05e73"><code>32fac6b</code></a>
Update <code>setup.cfg</code> files</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/ac7e449fbbc8a6e9347e65e4ced8176ba1655112"><code>ac7e449</code></a>
Add <code>.mcp.json</code> to the root folder</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/4014476e288d004a8de38eca582631c9c653fa22"><code>4014476</code></a>
Add a SeleniumBase MCP server</li>
<li>See full diff in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.52.1...v4.52.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=seleniumbase&package-manager=uv&previous-version=4.52.1&new-version=4.52.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-27 00:41:57 -04:00
Lance Marks f441b85da2 feat(packs): inspect multi-book releases and file each book separately (#1270)
## Multi-book packs: inspect a release before download and file each
book separately

Closes #576

### Problem

One queued release is always treated as one book. When a torrent is
actually a whole series
(`Series/Book 1 - Title/…`, or a flat folder of `Series 1.0 - Title.m4b`
files), post-processing
walks the whole tree, flattens every file into one list and renames them
`Title - 01…10` under the
searched book's `{Author}/{Title}`. Audiobookshelf then sees a single
10-file "book" and the user
has to re-file everything by hand.

### What this does

Most releases expose their file list *before* anything is downloaded, so
the split is decided up
front and approved by the user, then the download is fire-and-forget:

1. **Inspect** – clicking a release's download button now calls `POST
/api/releases/inspect`
first. A new optional `DownloadHandler.list_files(release_data)` hook
returns the release's
   files without downloading:
- **AudiobookBay** reads the torrent file table off the detail page it
already fetches (the
page is now cached for 120 s, so inspect + download cost ABB one
request).
- **Prowlarr** parses `info.files` from the `.torrent` it already
fetches (the existing 120 s
torrent-fetch cache is reused). Magnet-only and usenet releases report
"can't inspect".
   - Other sources default to `None`.
2. **Review** – if the plan contains more than one book, the Find
Releases modal swaps the list
for a review panel: one row per book with editable title / series
position / year, expandable
file lists, non-book sidecars (`.txt`, covers) shown as ignored, a
"Treat as a single book"
switch, and **Download N books**. Single-book releases queue
immediately, exactly as before.
3. **File** – the approved plan travels with the task
(`DownloadTask.book_plan`, retry-safe) and
post-processing files each book through the existing transfer code, one
book at a time
(`dataclasses.replace(task, title=…, series_position=…, year=…)`), so
organize/rename
templates, part numbering (now scoped per book), hardlinks, torrent
copy-preserve and usenet
   handling are unchanged. Status reads `Complete (N books, M files)`.
4. **Fallback** – when a release can't be inspected the user gets a
toast, and a small
"Multi-book pack" toggle in the modal header forces a heuristic split
(subfolder = book, or
   one book per file when the file names carry series positions).

Planning lives in `shelfmark/download/postprocess/packs.py` and is
shared by the inspect endpoint
and post-processing, so what the user approved is what gets filed. The
name parser strips
`Book 3 -`, `03 -`, `1.0 -`, `3.`, `[03]`, `#3`, a leading series name,
labels like
"An Expanse Novella -", repeated titles (`Gods of Risk 2.5 - Gods of
Risk`) and a trailing
`(Year)`; author and series name come from the book that was searched,
and the searched book's
own series position is never applied to its siblings.

### Files

- `shelfmark/download/postprocess/packs.py` (new) –
`PackFile/PackBook/PackPlan`, `plan_pack`,
`parse_pack_book_name`, `group_files_into_books`, `match_plan_to_files`
- `shelfmark/core/release_inspect_routes.py` (new) – `POST
/api/releases/inspect`
- `shelfmark/release_sources/__init__.py` – `DownloadHandler.list_files`
hook
- `shelfmark/release_sources/audiobookbay/{scraper,handler}.py` –
detail-page cache,
  `extract_file_list`, `list_files`
- `shelfmark/release_sources/prowlarr/handler.py`,
`download/clients/torrent_utils.py` –
  `extract_file_list_from_torrent`, `list_files`
- `shelfmark/core/models.py`, `download/orchestrator.py` – `multi_book`
/ `book_plan` fields,
  queue + retry serialization
- `shelfmark/download/postprocess/transfer.py`, `pipeline.py`,
`outputs/folder.py` – per-book
  transfer branch and status message
- `src/frontend`: `components/PackReviewPanel.tsx` (new),
`ReleaseModal.tsx`, `App.tsx`,
`services/api.ts`, `types/index.ts`, `utils/releasePayload.ts` (payload
builder moved out of
  `App.tsx`), `utils/packReview.ts`
- `docs/dev/release-sources-plugin-guide.md` – documents the
`list_files` hook

### Out of scope (follow-ups)

- Listing files from an NZB (Shelfmark already fetches the bytes; `<file
subject>` names are noisy)
- Inspecting magnet links via qBittorrent's files API after a paused add
- BookLore / email outputs (they ignore `book_plan`; noted in code)
- The combined ebook + audiobook flow

### Testing

**Automated** (`make checks`, `make python-test`, `make frontend-test`
all green; the only
failures on my machine are the pre-existing
`tests/config/test_entrypoint_permissions.py` cases,
which need bash ≥ 4 and fail identically on `main` under macOS bash
3.2):

- `tests/download/test_packs.py` – name parsing (markers, series name,
novella labels, repeated
titles, bare numeric titles like `1984`), nested / flat / mixed /
deeper-nested packs, single
wrapping folder not treated as a pack, plan-to-disk matching with
basename fallback
- `tests/core/test_processing_packs.py` – full `post_process_download`
runs on a real temp
filesystem: approved plan files each book under its own
`{Author}/{Title}`, heuristic split
of a nested pack, searched book's series position does not leak,
multi-file book inside a pack
keeps `- 01/- 02` per book, hardlinked torrent pack leaves the seeding
tree intact, no pack
fields ⇒ behaviour unchanged, single group degrades to the searched
title, status message
- `tests/core/test_release_inspect_routes.py` – plan response,
not-inspectable, handler errors
  never 500, unknown source / missing `source_id` ⇒ 400, login required
- `tests/audiobookbay/test_file_list.py` – file-table scraping from real
ABB markup (multi-file
and single-file pages), handler host validation, one page fetch shared
by magnet + file list
- `tests/prowlarr/test_torrent_file_list.py` – multi-file / single-file
`.torrent` parsing,
  handler behaviour for torrent URL vs magnet vs usenet vs cache miss
- `tests/download/test_orchestrator_pack_fields.py` – queue-time parsing
and retry round-trip
- Frontend: `releasePayload.test.ts`, `packReview.test.ts` (vitest)

**Manual, on a real deployment** (arm64 image built from this branch,
run as a side container
next to production with the same qBittorrent / Audiobookshelf setup,
`FILE_ORGANIZATION_AUDIOBOOK=organize`,
hardlinks on):

- AudiobookBay "The Expanse Complete 2.0" (7.87 GB, 36 files): clicking
download opened the review
panel in ~1 s showing **18 books · 18 files · 18 files ignored** (the
`.txt` sidecars), with
series positions 0.1–9.5 and years parsed from the file names; novella
labels stripped
("The Churn", "The Butcher of Anderson Station"). Editing a title in the
panel works.
Confirming queued one task; the magnet resolved from the cached page in
~30 ms; after the
download the task reported `Complete (18 books, 18 files)`, 18 hardlinks
landed as
`audiobooks/James S. A. Corey/<Title>/<Title>.m4b`, the torrent kept
seeding, and
Audiobookshelf scanned each folder as its own book (title, author,
embedded chapters).
- A second pack ("Expanse [01 - 9.5]", `Title N - Title` naming) was
inspected to verify the
  repeated-title rule and the Back button, without downloading.
- Single-book releases still queue immediately with no extra UI.
2026-08-27 00:40:08 -04:00
zab1996andRyan 02b7e9d958 feat(newznab): support multiple named indexers (#1271)
## Summary

- add a named Newznab indexer table with per-indexer URL and API key
settings
- search every configured indexer and retain the originating indexer
name on each result
- namespace cached release IDs across connections and isolate individual
indexer failures
- preserve the legacy single-indexer settings as a fallback
- support masked API-key cells and trusted SABnzbd prefetching for named
indexers

## Validation

- 121 Newznab and SABnzbd backend tests passed on Python 3.14
- Ruff passed for all changed Python files
- frontend TypeScript and strict lint checks passed
- all 134 frontend unit tests passed
- frontend formatting check passed

## Compatibility

Existing `NEWZNAB_URL` and `NEWZNAB_API_KEY` configurations continue to
work whenever `NEWZNAB_INDEXERS` is empty.

Co-authored-by: Ryan <zab1996@users.noreply.github.com>
2026-08-27 00:29:58 -04:00
CaliBrain ff06a1a581 fix(search): follow-ups to per-user book languages (#1267)
Review follow-ups to #1255, all in the code that PR touched.

Drop the dead user_id from the Prowlarr retry path.
ProwlarrSource.search
never reads plan.languages, and _refresh_release builds a synthetic book
with no titles_by_language, so the title variants came out identical
with
and without it. It also should not language-filter: it re-finds one
exact
release by its guid.

Pin the tab move in tests. BOOK_LANGUAGE moved from the General tab to
Search Mode with no migration, which only works because both tabs
persist
into the same settings.json. Nothing asserted that, so splitting the
files
later would silently reset every install to ["en"]. Covers the stored
value, a fresh install, and ENV precedence.

Stop the UI inventing a default language. An empty BOOK_LANGUAGE is a
deliberate "no default filter" that the backend preserves, but the two
frontend call sites replaced it with the first supported language, so
the
filter said English where the server filtered nothing.
resolveDefaultLanguageCodes
now falls back only when the value is absent.

Keep the normalized value for every validated search key.
validate_user_settings
gated the write-back on a hand-maintained subset of the keys the search
validator recognises, so METADATA_PROVIDER_COMBINED,
SHOW_COMBINED_SELECTOR
and FORCE_COMBINED_SEARCH were validated and then stored raw -- a padded
provider name was accepted and persisted with its padding. Reuse the
validator's own key set instead.

Skip blank language entries rather than rejecting them, so "" and "en,"
mean the same as [] and ["en"] instead of erroring on an unnamed
language.

Extract resolveListOverride for the list-override detection that was
copy-pasted between the two user-settings sections, and mention
languages
in the Search Preferences section description.
2026-08-24 18:11:29 -04:00
463ef49ac3 feat(search): let each user pick their own default book languages (#1255)
## Why

`BOOK_LANGUAGE` is a per-reader property, not a per-instance one. On a
shared install one household member searches in German while another
wants English and German — today whoever changes the setting changes it
for everyone, and the only escape is re-picking languages in the filter
on every single search.

The per-user override machinery already carries `SEARCH_MODE`, the
metadata providers and the default release sources, so the language
default mostly had to opt into it.

## What changed

**The field.** `BOOK_LANGUAGE` becomes `user_overridable` and moves from
the **General** tab to **Search Mode**, next to the other
user-overridable search defaults (per
[review](https://github.com/calibrain/shelfmark/pull/1255#issuecomment-5391189094)
— the first version had the Search section span two tabs, this one
doesn't). Admins set it per user in the user editor, users set it in
**My Account → Search Preferences**, and the Search Mode tab carries the
usual "N users override this" summary.

**No migration for the move.** `general` and `search_mode` both persist
into `settings.json`, and a field's value is resolved through
`load_config_file(tab)` for the tab it's declared on — so an install
that already stores `BOOK_LANGUAGE` keeps its value. Checked against a
`settings.json` written while the field still lived on General: the
stored value resolves unchanged, a fresh install still gets `["en"]`,
and `BOOK_LANGUAGE` in the environment still overrides both.

**The two places the default is read.**

- `/api/config` seeds the frontend's language filter, so it now resolves
`BOOK_LANGUAGE` for the session user.
- `build_release_search_plan` falls back to the default whenever a
request carries no language filter — which is exactly what the filter's
"Default" option sends. It takes an optional `user_id`, passed by
`/api/releases` from the session and by the Prowlarr retry path from
`task.user_id`, so a retry re-searches in the languages of whoever
queued the download.

**Validation.** Overrides go through `normalize_language()`, so
`"German"`, `"ger"` and `"de"` all store as `de`, and an unknown
language is rejected with a message naming it instead of being silently
searched for. An empty list stays an empty list (a deliberate "no
default filter"), `null` clears the override as everywhere else, and ENV
still wins: with `BOOK_LANGUAGE` set in the environment the field
reports `fromEnv` and overrides are ignored.

**Scope.** Only the language default becomes overridable. The two format
lists left behind under "Default Search Filters" stay admin-only — they
describe what the library and its post-processing accept, not what a
reader wants to read. There's a test pinning that.

## Verification

- 2681 unit tests pass (2670 before, 11 added)
- `ruff check`, `ruff format`, `basedpyright` over backend and tests,
and `vulture` all clean; frontend lint, format, typecheck and 126 unit
tests clean
- `docs/environment-variables.md` regenerated via
`scripts/generate_env_docs.py` (the `BOOK_LANGUAGE` row follows the
field into the Search Mode section)
- Manually against a two-user instance with builtin auth (first round,
before the tab move): with user A on German and user B on
English+German, `/api/config` returns each reader their own
`default_language` and an unfiltered `/api/releases` plans the matching
languages; an admin can set and read the same override for another user;
clearing it falls back to the global value; a stray `"klingon"` is
rejected; and `BOOK_LANGUAGE` in the environment overrides both users
with the field marked `fromEnv`
- After the tab move I re-ran the suites above plus the
stored-value/fresh-install/ENV check described under "No migration for
the move"; the behaviour it exercises is what the move could have broken

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: CaliBrain <calibrain@l4n.xyz>
2026-08-24 17:57:21 -04:00
CaliBrain a5595cf9f1 Change test for fake extension that wont work (#1266) 2026-08-24 17:54:10 -04:00
jakesterpdxandClaude Fable 5 9bcf595111 feat(prowlarr): warn when an indexer declares a format Shelfmark can't process (#1265)
## Problem

Companion to #1264, but general rather than mp4-specific.

MyAnonamouse titles carry a structured `[LANG / FORMATS]` bracket that
`_extract_mam_formats` parses. When every token in it is something
Shelfmark doesn't know — e.g. `The Martian by Andy Weir [ENG / MP4]` —
the release is rendered with **no format chip at all**, just the generic
headphones/book icon with an "Audiobook" tooltip. To a user that looks
like an ordinary result. It downloads fine and then fails
post-processing with *"No book files found in download"*.

The backend already *had* the signal (a format token it couldn't map);
it just threw it away.

## Change

**Backend** (`shelfmark/release_sources/prowlarr/source.py`)
- `_split_mam_formats(raw_title) -> (recognized, unrecognized)` replaces
the body of `_extract_mam_formats`, which is kept as a thin wrapper
returning `recognized` so nothing else changes.
- Releases gain `extra["unrecognized_formats"]` (list, or `None` when
empty / when format detection is off).

**Frontend**
- `getUnrecognizedReleaseFormats(release)` in `utils/releaseFormats.ts`
(normalised + deduped, same shape as `getReleaseFormats`).
- `ReleaseCell` `format_content_type`: when there is **no** recognised
format but the indexer named one, render an amber `MP4 Unsupported`
badge (compact view: amber `MP4`) with tooltip *"Unsupported format
(MP4) - Shelfmark cannot process this release"*. When a recognised
format exists the existing badge is untouched, even if extra unknown
tokens were present.

Only the chip changes — the download button still works, so a user can
still grab and hand-process the files if they want to. Happy to disable
the button instead if you'd prefer.

## Tests

- `tests/prowlarr/test_source.py`: `TestSplitMamFormats` (recognised /
unrecognised / mixed / no bracket / wrapper compat) and
`TestUnrecognizedFormatOnRelease` (lands in `extra`, empty when
recognised, absent without format detection).
- `src/frontend/src/tests/releaseFormats.test.ts`: 3 cases for the new
helper.
- `ruff check` clean; `pytest tests/prowlarr -m "not integration"` 511
passed; `tsc --noEmit`, `oxlint --deny warnings`, `vitest` all clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 17:50:27 -04:00
jakesterpdxandClaude Fable 5 65e2e3be20 feat(audiobook): recognise .mp4 as an audiobook format (#1264)
## Problem

Some trackers — MyAnonamouse in particular — distribute AAC audiobooks
as per-chapter `.mp4` files. That's the same ISO-BMFF container as
`.m4a`/`.m4b`, just with the generic extension (`ftyp isom`,
audio-only).

Today those releases:
1. show up in Prowlarr search results with **no format chip** — only the
generic "Audiobook" icon, because no format could be inferred;
2. download successfully; then
3. fail post-processing with **"No book files found in download"**,
because `.mp4` isn't in `AUDIOBOOK_FORMATS` (`shelfmark/core/utils.py`).

Real example: MAM #627978, *The Martian* (Andy Weir, 2020 edition) — 142
files `0001 … 0142 Andy Weir (2020) The Martian.mp4` + `cover.jpg`, 305
MB. Every file is a valid AAC-in-MP4 chapter.

Adding `mp4` to `SUPPORTED_AUDIOBOOK_FORMATS` in `settings.json` doesn't
help since the hard-coded tuple is what post-processing scans against.

## Change

- Add `"mp4"` to `AUDIOBOOK_FORMATS` (single source of truth — settings
UI, Prowlarr parsing, IRC parser, archive extraction and post-download
scan all derive from it), with a comment explaining why.
- Add `".mp4"` to the two hand-maintained debrid `_BOOK_EXTENSIONS`
lists (AllDebrid / Real-Debrid) so file selection matches.
- Slot `mp4` into the IRC `AUDIOBOOK_FORMAT_PRIORITY` table right after
`m4a` (same container family).
- Update the documented default in `docs/environment-variables.md`.
- New regression test
`test_audiobook_multifile_mp4_chapters_are_book_files` modelled on the
existing multi-file usenet test.

### Note for existing installs

The legacy-default migration only widens configs that still hold the old
`m4b,mp3` list, so users on the current widened default won't pick up
`mp4` automatically — they'll need to tick it in Settings → Audiobook
formats. New installs get it by default. Happy to extend the migration
if you'd rather it be automatic.

## Testing

- `ruff check` / `ruff format --check`: clean
- `pytest tests/core tests/config tests/irc tests/prowlarr
tests/download -m "not integration and not e2e"`: 2296 passed, new test
+ `test_audiobook_format_consistency.py` all green. The 10 failures in
`test_entrypoint_permissions.py` / `test_orchestrator_stall.py`
reproduce identically on untouched `main` on macOS and are unrelated.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 17:45:54 -04:00
CaliBrain ddc26f01b6 fix(download): escalating per-host cooldown on HTTP 429 (#1263)
Anna's Archive 429-throttles the source IP after repeated automated
requests.
The bypasser could clear the DDoS-Guard challenge but not the 429, so
each retry
re-solved, re-spawned Chrome, and rotated mirrors that share the same IP
- a
costly loop that never converged.

Add a process-global, per-host cooldown that escalates 2 -> 5 -> 10 ->
15 -> 30
minutes each time a host 429s again after its window elapsed, resetting
after a
long clear gap. Mirror selection skips cooling hosts and the bypasser
refuses to
solve one, so a throttled host fails fast instead of storming the
solver.
2026-08-24 13:08:07 -04:00
CaliBrain 89104ae80f fix(search): let manual search switch media type under forced combined search (#1262)
Manual search browses release sources directly, one media type at a
time,
so the combined (both) flow never applied to it — yet
FORCE_COMBINED_SEARCH
locked the content-type selector onto both, pinning manual search to
ebook
sources with no way to reach audiobook sources (no Audiobay tab).

Treat a manual search target as combined-exempt in the search bar:
present a
plain, switchable Books/Audiobooks selector (unlocked, no combined
toggle),
even when combined search is forced on for metadata targets. Metadata
search
behavior is unchanged.

Fixes #1256
2026-08-24 01:57:42 -04:00
dependabot[bot] 1e45add4d5 build(deps-dev): bump knip from 6.32.1 to 6.32.2 in /src/frontend in the npm-deps group (#1246)
Bumps the npm-deps group in /src/frontend with 1 update:
[knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip).

Updates `knip` from 6.32.1 to 6.32.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/webpro-nl/knip/releases">knip's
releases</a>.</em></p>
<blockquote>
<h2>Release 6.32.2</h2>
<ul>
<li>Support <code>oxfmt.config.mts</code> (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1933">#1933</a>)
(795900191dc75eec8d1e717b866bf57e1e2912cc) - thanks <a
href="https://github.com/joealden"><code>@​joealden</code></a>!</li>
<li>Support <code>oxlint.config.mts</code> (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1934">#1934</a>)
(531e2dc7c1d8bf31babea0068c34391182ec2d50) - thanks <a
href="https://github.com/joealden"><code>@​joealden</code></a>!</li>
<li>Fix Supported <code>lint-staged</code> Configs (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1935">#1935</a>)
(f9c755e414ed10baa4d01af8ddac6d04cb8d5617) - thanks <a
href="https://github.com/joealden"><code>@​joealden</code></a>!</li>
<li>Update dependencies (95f7c529f918dd9e1a84f92c68d064738977b825)</li>
<li>Update sentry snapshot
(ea7929fcbd6b323c8bdd9252ac57017feeb29ecf)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/webpro-nl/knip/commit/196a11a4e0cb5208c0dc8382a54341d5630af631"><code>196a11a</code></a>
Release knip@6.32.2</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/95f7c529f918dd9e1a84f92c68d064738977b825"><code>95f7c52</code></a>
Update dependencies</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/f9c755e414ed10baa4d01af8ddac6d04cb8d5617"><code>f9c755e</code></a>
Fix Supported <code>lint-staged</code> Configs (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1935">#1935</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/531e2dc7c1d8bf31babea0068c34391182ec2d50"><code>531e2dc</code></a>
Support <code>oxlint.config.mts</code> (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1934">#1934</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/795900191dc75eec8d1e717b866bf57e1e2912cc"><code>7959001</code></a>
Support <code>oxfmt.config.mts</code> (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1933">#1933</a>)</li>
<li>See full diff in <a
href="https://github.com/webpro-nl/knip/commits/knip@6.32.2/packages/knip">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=knip&package-manager=npm_and_yarn&previous-version=6.32.1&new-version=6.32.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-24 01:57:08 -04:00
dependabot[bot] cb3f6fee82 build(deps): bump astral-sh/uv from 0.11.3 to 0.12.5 in the docker-base-image-digests group (#1247)
Bumps the docker-base-image-digests group with 1 update:
[astral-sh/uv](https://github.com/astral-sh/uv).

Updates `astral-sh/uv` from 0.11.3 to 0.12.5
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/uv/releases">astral-sh/uv's
releases</a>.</em></p>
<blockquote>
<h2>0.12.5</h2>
<h2>Release Notes</h2>
<p>Released on 2026-08-14.</p>
<h3>Python</h3>
<ul>
<li>Add CPython 3.10.21, 3.11.16, and 3.12.14 (<a
href="https://redirect.github.com/astral-sh/uv/pull/21138">#21138</a>)</li>
<li>Prefer newer versions and standard variants when selecting between
equally prioritized Python interpreters (<a
href="https://redirect.github.com/astral-sh/uv/pull/21134">#21134</a>)</li>
</ul>
<h3>Enhancements</h3>
<ul>
<li>Simplify errors and hints for invalid editable requirements, and
redact credentials in requirement URLs (<a
href="https://redirect.github.com/astral-sh/uv/pull/21130">#21130</a>)</li>
</ul>
<h3>Preview features</h3>
<ul>
<li>Allow <code>--index</code> and <code>--default-index</code> to
select configured package indexes by name with the
<code>index-by-name</code> preview feature (<a
href="https://redirect.github.com/astral-sh/uv/pull/17455">#17455</a>)</li>
<li>Include distribution artifact URLs and hashes in CycloneDX SBOM
exports by default (<a
href="https://redirect.github.com/astral-sh/uv/pull/21131">#21131</a>)</li>
<li>Fall back to logical file sizes when using
<code>cache-physical-space</code> on filesystems that do not support
physical-space accounting (<a
href="https://redirect.github.com/astral-sh/uv/pull/21133">#21133</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Resolve relative package index paths in PEP 723 scripts against the
script directory (<a
href="https://redirect.github.com/astral-sh/uv/pull/21097">#21097</a>)</li>
</ul>
<h2>Install uv 0.12.5</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://releases.astral.sh/github/uv/releases/download/0.12.5/uv-installer.sh
| sh
</code></pre>
<h3>Install prebuilt binaries via powershell script</h3>
<pre lang="sh"><code>powershell -ExecutionPolicy Bypass -c &quot;irm
https://releases.astral.sh/github/uv/releases/download/0.12.5/uv-installer.ps1
| iex&quot;
</code></pre>
<h2>Download uv 0.12.5</h2>
<table>
<thead>
<tr>
<th>File</th>
<th>Platform</th>
<th>Checksum</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.5/uv-aarch64-apple-darwin.tar.gz">uv-aarch64-apple-darwin.tar.gz</a></td>
<td>Apple Silicon macOS</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.5/uv-aarch64-apple-darwin.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.5/uv-x86_64-apple-darwin.tar.gz">uv-x86_64-apple-darwin.tar.gz</a></td>
<td>Intel macOS</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.5/uv-x86_64-apple-darwin.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.5/uv-aarch64-pc-windows-msvc.zip">uv-aarch64-pc-windows-msvc.zip</a></td>
<td>ARM64 Windows</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.5/uv-aarch64-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.5/uv-i686-pc-windows-msvc.zip">uv-i686-pc-windows-msvc.zip</a></td>
<td>x86 Windows</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.5/uv-i686-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.5/uv-x86_64-pc-windows-msvc.zip">uv-x86_64-pc-windows-msvc.zip</a></td>
<td>x64 Windows</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.5/uv-x86_64-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.5/uv-aarch64-unknown-linux-gnu.tar.gz">uv-aarch64-unknown-linux-gnu.tar.gz</a></td>
<td>ARM64 Linux</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.5/uv-aarch64-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.5/uv-i686-unknown-linux-gnu.tar.gz">uv-i686-unknown-linux-gnu.tar.gz</a></td>
<td>x86 Linux</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.5/uv-i686-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.5/uv-powerpc64le-unknown-linux-gnu.tar.gz">uv-powerpc64le-unknown-linux-gnu.tar.gz</a></td>
<td>PPC64LE Linux</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.12.5/uv-powerpc64le-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
</tbody>
</table>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/uv/blob/main/CHANGELOG.md">astral-sh/uv's
changelog</a>.</em></p>
<blockquote>
<h2>0.12.5</h2>
<p>Released on 2026-08-14.</p>
<h3>Python</h3>
<ul>
<li>Add CPython 3.10.21, 3.11.16, and 3.12.14 (<a
href="https://redirect.github.com/astral-sh/uv/pull/21138">#21138</a>)</li>
<li>Prefer newer versions and standard variants when selecting between
equally prioritized Python interpreters (<a
href="https://redirect.github.com/astral-sh/uv/pull/21134">#21134</a>)</li>
</ul>
<h3>Enhancements</h3>
<ul>
<li>Simplify errors and hints for invalid editable requirements, and
redact credentials in requirement URLs (<a
href="https://redirect.github.com/astral-sh/uv/pull/21130">#21130</a>)</li>
</ul>
<h3>Preview features</h3>
<ul>
<li>Allow <code>--index</code> and <code>--default-index</code> to
select configured package indexes by name with the
<code>index-by-name</code> preview feature (<a
href="https://redirect.github.com/astral-sh/uv/pull/17455">#17455</a>)</li>
<li>Include distribution artifact URLs and hashes in CycloneDX SBOM
exports by default (<a
href="https://redirect.github.com/astral-sh/uv/pull/21131">#21131</a>)</li>
<li>Fall back to logical file sizes when using
<code>cache-physical-space</code> on filesystems that do not support
physical-space accounting (<a
href="https://redirect.github.com/astral-sh/uv/pull/21133">#21133</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Resolve relative package index paths in PEP 723 scripts against the
script directory (<a
href="https://redirect.github.com/astral-sh/uv/pull/21097">#21097</a>)</li>
</ul>
<h2>0.12.4</h2>
<p>Released on 2026-08-13.</p>
<h3>Enhancements</h3>
<ul>
<li>Prefer post-quantum key exchange and enable opt-in TLS diagnostics
(<a
href="https://redirect.github.com/astral-sh/uv/pull/21054">#21054</a>)</li>
<li>Accept whitespace before versions in noncompliant wildcard
comparisons such as <code>Requires-Python: &gt;= 3.5.*</code> (<a
href="https://redirect.github.com/astral-sh/uv/pull/21012">#21012</a>)</li>
<li>Report a specific error when a PEP 723 closing tag contains trailing
whitespace or other content (<a
href="https://redirect.github.com/astral-sh/uv/pull/20944">#20944</a>)</li>
<li>Omit source-span carets from diagnostics for empty PEP 508
requirements (<a
href="https://redirect.github.com/astral-sh/uv/pull/21094">#21094</a>)</li>
</ul>
<h3>Preview features</h3>
<ul>
<li>Add <code>uv check --no-install-project</code> and respect
<code>UV_NO_INSTALL_PROJECT</code> to install dependencies without
building or installing the project (<a
href="https://redirect.github.com/astral-sh/uv/pull/21085">#21085</a>)</li>
<li>Make the ty subprocess invoked by <code>uv check</code> honor uv's
color and progress settings, including quiet mode (<a
href="https://redirect.github.com/astral-sh/uv/pull/21086">#21086</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Speed up resolutions with long runs of unavailable package versions
by coalescing gaps in the resolver's version ranges (<a
href="https://redirect.github.com/astral-sh/uv/pull/20804">#20804</a>)</li>
<li>Speed up Simple API parsing by deserializing PyPI and Pyx file
metadata directly (<a
href="https://redirect.github.com/astral-sh/uv/pull/21041">#21041</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Use windowed <code>pythonw.exe</code> launchers for virtual
environments created from managed Python minor-version links (<a
href="https://redirect.github.com/astral-sh/uv/pull/19235">#19235</a>)</li>
<li>Allow <code>uv lock</code> to proceed when <code>.venv</code> is an
unusable project environment (<a
href="https://redirect.github.com/astral-sh/uv/pull/21068">#21068</a>)</li>
<li>Respect <code>fork-strategy</code> when ordering forks created from
<code>environments</code> or existing lockfile
<code>resolution-markers</code> (<a
href="https://redirect.github.com/astral-sh/uv/pull/21000">#21000</a>)</li>
<li>Preserve consecutive wildcard Python minor-version exclusions such
as <code>!=3.11.*, !=3.12.*</code> in <code>uv.lock</code> (<a
href="https://redirect.github.com/astral-sh/uv/pull/21045">#21045</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/uv/commit/210d1f6785e95a8c8c0d53e284408c9be1134700"><code>210d1f6</code></a>
Bump version to 0.12.5 (<a
href="https://redirect.github.com/astral-sh/uv/issues/21140">#21140</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/802a9162a1153ddee799a0080c80a38d96009d91"><code>802a916</code></a>
Sync latest Python releases: 3.10.21, 3.11.16, 3.12.14 (<a
href="https://redirect.github.com/astral-sh/uv/issues/21138">#21138</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/a6904bbdabd3c6ac42b1051896ff597c8bd1895a"><code>a6904bb</code></a>
Order equal-priority Python installations by key (<a
href="https://redirect.github.com/astral-sh/uv/issues/21134">#21134</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/728a70d25c0889d9aaa53776256c3a843eea67a6"><code>728a70d</code></a>
Improve automated fixes for related bug manifestations (<a
href="https://redirect.github.com/astral-sh/uv/issues/21102">#21102</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/b82b0383fc3471227aa864f18d2c1c8efac8b9f1"><code>b82b038</code></a>
Include hashes in cyclonedx exports (<a
href="https://redirect.github.com/astral-sh/uv/issues/21131">#21131</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/8011778d41f2c96fb0b856fb3cbcb4ec8d28cb63"><code>8011778</code></a>
Simplify editable requirement errors and hints (<a
href="https://redirect.github.com/astral-sh/uv/issues/21130">#21130</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/dca33f5e6799f2aefee52729732a839a3b358740"><code>dca33f5</code></a>
Fall back to logical cache accounting on unsupported filesystems (<a
href="https://redirect.github.com/astral-sh/uv/issues/21133">#21133</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/3a76e496e36783371a6d91a8f4834478964d36ca"><code>3a76e49</code></a>
Get rid of <code>Lock::with_manifest</code>, make
<code>Lock::from_resolution</code> take the manif...</li>
<li><a
href="https://github.com/astral-sh/uv/commit/7e6caa45f62a0506fea1b8ecfa0558e5d62d520c"><code>7e6caa4</code></a>
Support referencing indexes by name via <code>--index</code> and
<code>--default-index</code> (<a
href="https://redirect.github.com/astral-sh/uv/issues/17455">#17455</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/298dda41bff49bbd0a2f397e1662ab09b9cd148e"><code>298dda4</code></a>
Fix relative indexes in PEP 723 scripts (<a
href="https://redirect.github.com/astral-sh/uv/issues/21097">#21097</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/uv/compare/0.11.3...0.12.5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=astral-sh/uv&package-manager=docker&previous-version=0.11.3&new-version=0.12.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-24 01:57:00 -04:00
dependabot[bot] 0dc13c1ca4 build(deps): bump the gh-actions group with 5 updates (#1248)
Bumps the gh-actions group with 5 updates:

| Package | From | To |
| --- | --- | --- |
|
[docker/setup-buildx-action](https://github.com/docker/setup-buildx-action)
| `4.2.0` | `4.3.0` |
| [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) | `9.0.0`
| `10.0.1` |
| [github/codeql-action/init](https://github.com/github/codeql-action) |
`4.37.6` | `4.37.7` |
|
[github/codeql-action/autobuild](https://github.com/github/codeql-action)
| `4.37.6` | `4.37.7` |
|
[github/codeql-action/analyze](https://github.com/github/codeql-action)
| `4.37.6` | `4.37.7` |

Updates `docker/setup-buildx-action` from 4.2.0 to 4.3.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/docker/setup-buildx-action/releases">docker/setup-buildx-action's
releases</a>.</em></p>
<blockquote>
<h2>v4.3.0</h2>
<ul>
<li>Bump <code>@​docker/actions-toolkit</code> from 0.92.0 to 0.95.0 in
<a
href="https://redirect.github.com/docker/setup-buildx-action/pull/595">docker/setup-buildx-action#595</a></li>
<li>Bump brace-expansion from 1.1.13 to 1.1.18 in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/600">docker/setup-buildx-action#600</a></li>
<li>Bump js-yaml from 5.2.0 to 5.3.0 in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/585">docker/setup-buildx-action#585</a></li>
<li>Bump postcss from 8.5.10 to 8.5.25 in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/598">docker/setup-buildx-action#598</a></li>
<li>Bump undici from 6.27.0 to 6.28.0 in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/601">docker/setup-buildx-action#601</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/setup-buildx-action/compare/v4.2.0...v4.3.0">https://github.com/docker/setup-buildx-action/compare/v4.2.0...v4.3.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/37fe631027851001ddb9b187196cc803df7f5f0e"><code>37fe631</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/setup-buildx-action/issues/595">#595</a>
from docker/dependabot/npm_and_yarn/docker/actions-to...</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/b5c4f91922681cc7c58d15ab7838986951f09d19"><code>b5c4f91</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/3e93b637c6430ba8fa896fad44d3aa6821899d63"><code>3e93b63</code></a>
build(deps): bump <code>@​docker/actions-toolkit</code> from 0.92.0 to
0.95.0</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/e527031b32c86649307d5d492506855f90470604"><code>e527031</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/setup-buildx-action/issues/600">#600</a>
from docker/dependabot/npm_and_yarn/brace-expansion-1...</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/c68814b33cb66f1f7538e546190d410ae557a640"><code>c68814b</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/3f891b01bd5012a434f582800366972569aa1886"><code>3f891b0</code></a>
build(deps): bump brace-expansion from 1.1.13 to 1.1.18</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/787db26fcde8ddcabd49a81472318028f7113962"><code>787db26</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/setup-buildx-action/issues/585">#585</a>
from docker/dependabot/npm_and_yarn/js-yaml-5.2.1</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/f7793687c711790ca336bd4934f1b1bf5f778e17"><code>f779368</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/7d5e60413489a33d28077e11d71c668580cfaf8d"><code>7d5e604</code></a>
build(deps): bump js-yaml from 5.2.0 to 5.3.0</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/292c2fb3837a12d3ac2d1e47bbc5c00712bad939"><code>292c2fb</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/setup-buildx-action/issues/590">#590</a>
from docker/dependabot/github_actions/actions/setup-n...</li>
<li>Additional commits viewable in <a
href="https://github.com/docker/setup-buildx-action/compare/bb05f3f5519dd87d3ba754cc423b652a5edd6d2c...37fe631027851001ddb9b187196cc803df7f5f0e">compare
view</a></li>
</ul>
</details>
<br />

Updates `astral-sh/setup-uv` from 9.0.0 to 10.0.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/setup-uv/releases">astral-sh/setup-uv's
releases</a>.</em></p>
<blockquote>
<h2>v10.0.1 🌈 Tolerate transient manifest timeouts</h2>
<h2>Changes</h2>
<p>Thank you <a
href="https://github.com/arguile"><code>@​arguile</code></a>- for making
this action more resilient.</p>
<h2>🐛 Bug fixes</h2>
<ul>
<li>Tolerate transient manifest timeouts <a
href="https://github.com/arguile"><code>@​arguile</code></a>- (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1016">#1016</a>)</li>
</ul>
<h2>🧰 Maintenance</h2>
<ul>
<li>chore: update known checksums for 0.12.4 @<a
href="https://github.com/apps/github-actions">github-actions[bot]</a>
(<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1017">#1017</a>)</li>
</ul>
<h2>📚 Documentation</h2>
<ul>
<li>docs: update version references to v10.0.0 @<a
href="https://github.com/apps/github-actions">github-actions[bot]</a>
(<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1014">#1014</a>)</li>
</ul>
<h2>v10.0.0 🌈 Disable automatic caching for sensitive events and new QOL
features</h2>
<h2>Changes</h2>
<p>Another breaking release, directly after v9.0.0 but we think the
added security justifies that.</p>
<h3>Extra security by default</h3>
<p>If you use the default <code>enable-cache: auto</code> this will now
<strong>DISABLE THE CACHE</strong> to protect against cache poisoning
for the following events:</p>
<ul>
<li><code>pull_request_target</code></li>
<li><code>workflow_run</code></li>
<li><code>release</code></li>
</ul>
<p>You can read the full reasoning in <a
href="https://redirect.github.com/astral-sh/setup-uv/issues/984">astral-sh/setup-uv#984</a></p>
<h3><code>version: latest-known</code></h3>
<pre lang="yaml"><code>- name: Install the latest version of uv known to
setup-uv
  uses: astral-sh/setup-uv@v10.0.0
  with:
    version: &quot;latest-known&quot;
</code></pre>
<p>This will now install the latest version with a checksum that is
known by this action. The <a
href="https://github.com/astral-sh/setup-uv/blob/4f6036f71cec78afb113b323f220c9185d983c12/src/download/checksum/known-checksums.ts">known
<code>uv</code> checksums</a> are automatically updated but will take a
release of this action to take effect. You won't be always using the
latest &amp; greatest but you will have an extra level of security.</p>
<h3>Read python version from <code>.tool-versions</code></h3>
<pre lang="yaml"><code>- name: Install uv based on the version defined
in .tool-versions and also set python
  uses: astral-sh/setup-uv@v10.0.0
  with:
    version-file: &quot;pyproject.toml&quot;
&lt;/tr&gt;&lt;/table&gt; 
</code></pre>
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/20cfd1bf945f4377ade1205e4dbc17946fc9a30d"><code>20cfd1b</code></a>
chore: update known checksums for 0.12.4 (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1017">#1017</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/d73a0cab66a532d7afa440d9df4a67ea9fe65a30"><code>d73a0ca</code></a>
Tolerate transient manifest timeouts (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1016">#1016</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/ae3b92d1bdb308a10adfe7b8f408e5cc8c30f3f6"><code>ae3b92d</code></a>
docs: update version references to v10.0.0 (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1014">#1014</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/ae62891fec2bb8e7d6c99fc78c9fec3a63790f8d"><code>ae62891</code></a>
chore(deps): roll up Dependabot updates (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1013">#1013</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/f9cdb47d487aee2be8925d1e57290177ad9e1ac2"><code>f9cdb47</code></a>
Reject paths in .tool-versions (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1007">#1007</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/4f6036f71cec78afb113b323f220c9185d983c12"><code>4f6036f</code></a>
Require pull requests for Dependabot rollups (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1005">#1005</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/8d6402c9b71205b2d8d0b82de531d8fed8430182"><code>8d6402c</code></a>
chore(deps): roll up Dependabot updates (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1004">#1004</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/46f427bd47c794e99536b75ffaa9f27602425027"><code>46f427b</code></a>
Read Python version from .tool-versions (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/996">#996</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/8ed89c51143f65ea13eaba62db51dbb8ea52d0a3"><code>8ed89c5</code></a>
ci: pin Alpine container image (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/995">#995</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/8473c7fea42cdfd540f4b01317a17ac5f54126ae"><code>8473c7f</code></a>
chore(deps): roll up Dependabot updates (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/994">#994</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/setup-uv/compare/c771a70e6277c0a99b617c7a806ffedaca235ff9...20cfd1bf945f4377ade1205e4dbc17946fc9a30d">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/init` from 4.37.6 to 4.37.7
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/init's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.7</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/init's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.7 - 13 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd"><code>ff2f1c6</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4093">#4093</a>
from github/update-v4.37.7-be7a3dbb8</li>
<li><a
href="https://github.com/github/codeql-action/commit/951a133f96aa2114dd747e9e437305335d0bde16"><code>951a133</code></a>
Update changelog for v4.37.7</li>
<li><a
href="https://github.com/github/codeql-action/commit/be7a3dbb8147b82cd6d27e0707105b36aa190fc1"><code>be7a3db</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4087">#4087</a>
from github/dependabot/npm_and_yarn/npm-minor-0aa561...</li>
<li><a
href="https://github.com/github/codeql-action/commit/9310334b11405b305d9444edfa56cd86e2f1e4fe"><code>9310334</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4086">#4086</a>
from github/mbg/thread-action-state-to-codeql</li>
<li><a
href="https://github.com/github/codeql-action/commit/b4d8a54218a8792de9af2f6f32e33af899ca5212"><code>b4d8a54</code></a>
Rebuild</li>
<li><a
href="https://github.com/github/codeql-action/commit/ab5db2519c3344f2fa61c711fa2d6ad135829200"><code>ab5db25</code></a>
Bump the npm-minor group across 1 directory with 8 updates</li>
<li><a
href="https://github.com/github/codeql-action/commit/38055a3c3cf3979323eaf70fc6c73a8690250bde"><code>38055a3</code></a>
Drop <code>logger</code> from <code>databaseInitCluster</code> in
interface</li>
<li><a
href="https://github.com/github/codeql-action/commit/1f87aed5e66849f0c43ae147377cc77f2d98ac99"><code>1f87aed</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4085">#4085</a>
from github/update-bundle/codeql-bundle-v2.26.3</li>
<li><a
href="https://github.com/github/codeql-action/commit/dc1b98ad1c2f13ccf9fc33fb82f32fc76f944253"><code>dc1b98a</code></a>
Make <code>logger</code> available to <code>getCodeQLForCmd</code></li>
<li><a
href="https://github.com/github/codeql-action/commit/6f0220ee37121218af472efbde25f06907a4da4f"><code>6f0220e</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4084">#4084</a>
from github/navntoft/bump-undici</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/5595ccaf912efad79be6eef63a5619ff05969be3...ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/autobuild` from 4.37.6 to 4.37.7
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/autobuild's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.7</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/autobuild's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.7 - 13 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd"><code>ff2f1c6</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4093">#4093</a>
from github/update-v4.37.7-be7a3dbb8</li>
<li><a
href="https://github.com/github/codeql-action/commit/951a133f96aa2114dd747e9e437305335d0bde16"><code>951a133</code></a>
Update changelog for v4.37.7</li>
<li><a
href="https://github.com/github/codeql-action/commit/be7a3dbb8147b82cd6d27e0707105b36aa190fc1"><code>be7a3db</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4087">#4087</a>
from github/dependabot/npm_and_yarn/npm-minor-0aa561...</li>
<li><a
href="https://github.com/github/codeql-action/commit/9310334b11405b305d9444edfa56cd86e2f1e4fe"><code>9310334</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4086">#4086</a>
from github/mbg/thread-action-state-to-codeql</li>
<li><a
href="https://github.com/github/codeql-action/commit/b4d8a54218a8792de9af2f6f32e33af899ca5212"><code>b4d8a54</code></a>
Rebuild</li>
<li><a
href="https://github.com/github/codeql-action/commit/ab5db2519c3344f2fa61c711fa2d6ad135829200"><code>ab5db25</code></a>
Bump the npm-minor group across 1 directory with 8 updates</li>
<li><a
href="https://github.com/github/codeql-action/commit/38055a3c3cf3979323eaf70fc6c73a8690250bde"><code>38055a3</code></a>
Drop <code>logger</code> from <code>databaseInitCluster</code> in
interface</li>
<li><a
href="https://github.com/github/codeql-action/commit/1f87aed5e66849f0c43ae147377cc77f2d98ac99"><code>1f87aed</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4085">#4085</a>
from github/update-bundle/codeql-bundle-v2.26.3</li>
<li><a
href="https://github.com/github/codeql-action/commit/dc1b98ad1c2f13ccf9fc33fb82f32fc76f944253"><code>dc1b98a</code></a>
Make <code>logger</code> available to <code>getCodeQLForCmd</code></li>
<li><a
href="https://github.com/github/codeql-action/commit/6f0220ee37121218af472efbde25f06907a4da4f"><code>6f0220e</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4084">#4084</a>
from github/navntoft/bump-undici</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/5595ccaf912efad79be6eef63a5619ff05969be3...ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/analyze` from 4.37.6 to 4.37.7
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/analyze's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.7</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/analyze's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.7 - 13 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd"><code>ff2f1c6</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4093">#4093</a>
from github/update-v4.37.7-be7a3dbb8</li>
<li><a
href="https://github.com/github/codeql-action/commit/951a133f96aa2114dd747e9e437305335d0bde16"><code>951a133</code></a>
Update changelog for v4.37.7</li>
<li><a
href="https://github.com/github/codeql-action/commit/be7a3dbb8147b82cd6d27e0707105b36aa190fc1"><code>be7a3db</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4087">#4087</a>
from github/dependabot/npm_and_yarn/npm-minor-0aa561...</li>
<li><a
href="https://github.com/github/codeql-action/commit/9310334b11405b305d9444edfa56cd86e2f1e4fe"><code>9310334</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4086">#4086</a>
from github/mbg/thread-action-state-to-codeql</li>
<li><a
href="https://github.com/github/codeql-action/commit/b4d8a54218a8792de9af2f6f32e33af899ca5212"><code>b4d8a54</code></a>
Rebuild</li>
<li><a
href="https://github.com/github/codeql-action/commit/ab5db2519c3344f2fa61c711fa2d6ad135829200"><code>ab5db25</code></a>
Bump the npm-minor group across 1 directory with 8 updates</li>
<li><a
href="https://github.com/github/codeql-action/commit/38055a3c3cf3979323eaf70fc6c73a8690250bde"><code>38055a3</code></a>
Drop <code>logger</code> from <code>databaseInitCluster</code> in
interface</li>
<li><a
href="https://github.com/github/codeql-action/commit/1f87aed5e66849f0c43ae147377cc77f2d98ac99"><code>1f87aed</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4085">#4085</a>
from github/update-bundle/codeql-bundle-v2.26.3</li>
<li><a
href="https://github.com/github/codeql-action/commit/dc1b98ad1c2f13ccf9fc33fb82f32fc76f944253"><code>dc1b98a</code></a>
Make <code>logger</code> available to <code>getCodeQLForCmd</code></li>
<li><a
href="https://github.com/github/codeql-action/commit/6f0220ee37121218af472efbde25f06907a4da4f"><code>6f0220e</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4084">#4084</a>
from github/navntoft/bump-undici</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/5595ccaf912efad79be6eef63a5619ff05969be3...ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-24 01:56:52 -04:00
dependabot[bot] d1f8527089 build(deps): bump the python-deps group across 1 directory with 5 updates (#1260)
Bumps the python-deps group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [gunicorn](https://github.com/benoitc/gunicorn) | `26.0.0` | `26.1.0`
|
| [apprise](https://github.com/caronc/apprise) | `1.12.0` | `1.13.0` |
| [seleniumbase](https://github.com/seleniumbase/SeleniumBase) |
`4.51.12` | `4.52.1` |
| [prek](https://github.com/j178/prek) | `0.4.13` | `0.4.14` |
| [ruff](https://github.com/astral-sh/ruff) | `0.16.3` | `0.16.4` |


Updates `gunicorn` from 26.0.0 to 26.1.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/benoitc/gunicorn/releases">gunicorn's
releases</a>.</em></p>
<blockquote>
<h2>gunicorn 26.1.0</h2>
<h3>New Features</h3>
<ul>
<li><strong>Glob patterns in <code>reload_extra_files</code></strong>:
entries containing <code>*</code>, <code>?</code> or <code>[</code>
are treated as patterns, so <code>ui/*/config.json</code> watches every
view's config
without listing them one by one. Patterns are re-expanded on every
reload
check rather than once at startup, so a file created later starts being
watched without restarting gunicorn, and <code>**</code> recurses. A
pattern matching
nothing warns instead of failing, since with live expansion it may match
later
(<a
href="https://redirect.github.com/benoitc/gunicorn/issues/1643">#1643</a>,
<a
href="https://redirect.github.com/benoitc/gunicorn/pull/3662">#3662</a>).</li>
</ul>
<h3>Security</h3>
<ul>
<li><strong>Dependency floors raised past known advisories</strong>:
every declared floor was
checked against the advisory database. <code>tornado</code>,
<code>h2</code>, <code>setuptools</code> and
<code>pymdown-extensions</code> permitted vulnerable versions and now
require the first
clean release; <code>pytest</code> and <code>httpx</code> were unpinned
and now carry floors. The
<code>tornado</code> example pinned <code>tornado&lt;6</code>, which was
both the source of several
advisories and older than the <code>&gt;=6.5.0</code> the tornado worker
needs, so the
example could not run as pinned.</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>
<p><strong>SIGHUP did not reload the logger configuration</strong>:
<code>Arbiter.reload()</code>
re-read the configuration file but kept using the logger built at
startup,
calling only <code>reopen_files()</code> on its existing handlers.
Changes to
<code>logconfig</code>, <code>logconfig_dict</code>,
<code>logconfig_json</code> and <code>loglevel</code> were ignored
until a full restart, which in containers meant replacing the pod. The
existing logger now re-runs its setup on reload, so new handlers,
formats
and levels take effect while the process identity and its listeners are
preserved, and re-running the setup no longer stacks duplicate syslog
handlers. An invalid log configuration on reload is not fatal either:
the
error is reported on stderr, the previous working configuration is
restored
and the master keeps running with it
(<a
href="https://redirect.github.com/benoitc/gunicorn/issues/3353">#3353</a>).</p>
</li>
<li>
<p><strong>Truncated chunked bodies accepted</strong>: RFC 9112 section
7.1.2 ends a chunked
body with <code>0 CRLF CRLF</code>, the second CRLF being the mandatory
empty trailer
section. <code>ChunkedReader.parse_chunk_size()</code> swallowed the
<code>NoMoreData</code> raised
while scanning for it, so a body cut short right after the last chunk
line was
treated as complete instead of rejected. It now raises
<code>ChunkMissingTerminator</code>
(<a
href="https://redirect.github.com/benoitc/gunicorn/issues/3382">#3382</a>,
<a
href="https://redirect.github.com/benoitc/gunicorn/pull/3685">#3685</a>).</p>
</li>
<li>
<p><strong><code>--spew</code> crashed on dynamically generated
code</strong>: the trace hook indexed the
2-tuple returned by <code>inspect.getsourcelines()</code> by line number
rather than
indexing the list of lines, so a frame with no <code>__file__</code>
raised
<code>AttributeError: 'int' object has no attribute 'rstrip'</code> on
line 1 and</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/benoitc/gunicorn/commit/71b59a75820dd4a762dc42a3280124168b4e44a8"><code>71b59a7</code></a>
Merge pull request <a
href="https://redirect.github.com/benoitc/gunicorn/issues/3698">#3698</a>
from benoitc/fix/docker-health-check-readerror</li>
<li><a
href="https://github.com/benoitc/gunicorn/commit/48287de8d8360825c85ac981ddfe9c80dba3b418"><code>48287de</code></a>
test: catch every transport error in the docker health check</li>
<li><a
href="https://github.com/benoitc/gunicorn/commit/3110e8c37f716ca0ed63ca7f80c7c9ea0fbbdd50"><code>3110e8c</code></a>
Merge pull request <a
href="https://redirect.github.com/benoitc/gunicorn/issues/3696">#3696</a>
from benoitc/docs/roadmap</li>
<li><a
href="https://github.com/benoitc/gunicorn/commit/cc56c410b7103c1f2b877279f81cb3a14705315a"><code>cc56c41</code></a>
Merge pull request <a
href="https://redirect.github.com/benoitc/gunicorn/issues/3693">#3693</a>
from benoitc/release/26.1.0</li>
<li><a
href="https://github.com/benoitc/gunicorn/commit/5cf1f1651a40fa36afe13fde623e3437ca872463"><code>5cf1f16</code></a>
docs: surface the roadmap on the site home page</li>
<li><a
href="https://github.com/benoitc/gunicorn/commit/7e35f72d135056c37da321becf22b19aeec06937"><code>7e35f72</code></a>
docs: add FastCGI to the roadmap and point items at Ideas</li>
<li><a
href="https://github.com/benoitc/gunicorn/commit/18ddc586c9b9513b25e92c6ed6f818f66ff3abcd"><code>18ddc58</code></a>
docs: drop the framework and reverse-proxy non-goals from the
roadmap</li>
<li><a
href="https://github.com/benoitc/gunicorn/commit/1ecae56ebd096ee4f94d12c59a36f240ce348572"><code>1ecae56</code></a>
docs: add a roadmap and make the chat easy to find</li>
<li><a
href="https://github.com/benoitc/gunicorn/commit/ca412e3f7134bd9f0e91851778ae6d8dd222cf41"><code>ca412e3</code></a>
docs: sync the Latest changelog page with 26.1.0</li>
<li><a
href="https://github.com/benoitc/gunicorn/commit/640936fb29f7bdf5665d5b7f919783bb73e2f981"><code>640936f</code></a>
docs: note the dependency security work in 26.1.0</li>
<li>Additional commits viewable in <a
href="https://github.com/benoitc/gunicorn/compare/26.0.0...26.1.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `apprise` from 1.12.0 to 1.13.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/caronc/apprise/releases">apprise's
releases</a>.</em></p>
<blockquote>
<h2>Release v1.13.0</h2>
<h2>What's Changed</h2>
<p>This update clears out our backlog of resolved bugs and introduces a
few new services. While it’s a standard maintenance release, we want to
give a massive shoutout to our community. The level of support we've
received for this update is truly incredible; there were more
commits/PRs from you than there were from me and that has never happend
before 🙂 . Your contributions are deeply
appreciated! Seriously!</p>
<p><strong>Note:</strong> ⚠️ <strong>This will be the last version of
Apprise v1.x</strong>. Future releases will be under Apprise v2.x. This
means: Developers, <em>please</em> make sure you pin your
<code>projects.toml</code> or <code>requirements.txt</code> to
<code>apprise &gt;=1.0.0, &lt;2.0.0</code>. v2.x will be awesome, but it
will introduce breaking changes your software may need to adapt to. I
will still continue supporting the v1.x branch should security issues
arise for at year or two, but eventually v2.x would be the way to
go.</p>
<h3>📣 New Notification Services:</h3>
<ul>
<li>Added Pinglet Support by <a
href="https://github.com/TheGlenn88"><code>@​TheGlenn88</code></a> in <a
href="https://redirect.github.com/caronc/apprise/pull/1680">caronc/apprise#1680</a></li>
<li>Add Trigv notification support by <a
href="https://github.com/hchouhan"><code>@​hchouhan</code></a> in <a
href="https://redirect.github.com/caronc/apprise/pull/1671">caronc/apprise#1671</a></li>
<li>Added Pingram (formerly NotificationApi) by <a
href="https://github.com/lipusal"><code>@​lipusal</code></a> in <a
href="https://redirect.github.com/caronc/apprise/pull/1665">caronc/apprise#1665</a></li>
</ul>
<h3>🐞  Bugfixes</h3>
<ul>
<li>Preserve x/y coordinate of 0 in dbus/glib url() (round-trip crash on
x=0) by <a href="https://github.com/gaoflow"><code>@​gaoflow</code></a>
in <a
href="https://redirect.github.com/caronc/apprise/pull/1663">caronc/apprise#1663</a></li>
<li>Allow RFC 3986 path characters that are safe to leave unencoded by
<a href="https://github.com/Sanjays2402"><code>@​Sanjays2402</code></a>
in <a
href="https://redirect.github.com/caronc/apprise/pull/1673">caronc/apprise#1673</a></li>
<li>small bugfix in splunk entity_id handling on url in <a
href="https://redirect.github.com/caronc/apprise/pull/1688">caronc/apprise#1688</a></li>
<li>Fix Microsoft Workflows CU-routed webhooks by <a
href="https://github.com/Sanjays2402"><code>@​Sanjays2402</code></a> in
<a
href="https://redirect.github.com/caronc/apprise/pull/1676">caronc/apprise#1676</a></li>
<li>Return None from parse_url() on malformed authority content (<a
href="https://redirect.github.com/caronc/apprise/issues/1693">#1693</a>)
by <a href="https://github.com/youdie006"><code>@​youdie006</code></a>
in <a
href="https://redirect.github.com/caronc/apprise/pull/1694">caronc/apprise#1694</a></li>
<li>fix issue causing in memory email attachments to fail by <a
href="https://github.com/kevinfeyrer"><code>@​kevinfeyrer</code></a> in
<a
href="https://redirect.github.com/caronc/apprise/pull/1696">caronc/apprise#1696</a></li>
</ul>
<h3>💡 Features</h3>
<ul>
<li>Improved handling matrix message splitting in <a
href="https://redirect.github.com/caronc/apprise/pull/1687">caronc/apprise#1687</a></li>
<li>feat(bark): add AES-GCM encryption by <a
href="https://github.com/IceCodeNew"><code>@​IceCodeNew</code></a> in <a
href="https://redirect.github.com/caronc/apprise/pull/1684">caronc/apprise#1684</a></li>
<li>fix(docs): CLI file attachments feature by <a
href="https://github.com/egvimo"><code>@​egvimo</code></a> in <a
href="https://redirect.github.com/caronc/apprise/pull/1678">caronc/apprise#1678</a></li>
<li>Support for Telegram rich message support in <a
href="https://redirect.github.com/caronc/apprise/pull/1690">caronc/apprise#1690</a></li>
</ul>
<h3>❤️ Life-Cycle Support</h3>
<ul>
<li>Fix grammar and tiny errors in README.md by <a
href="https://github.com/notrudyyy"><code>@​notrudyyy</code></a> in <a
href="https://redirect.github.com/caronc/apprise/pull/1674">caronc/apprise#1674</a></li>
<li>docs: quote pip extras install examples by <a
href="https://github.com/nyxst4ck"><code>@​nyxst4ck</code></a> in <a
href="https://redirect.github.com/caronc/apprise/pull/1679">caronc/apprise#1679</a></li>
<li>Decomissioned legacy service NotificationApi by <a
href="https://github.com/lipusal"><code>@​lipusal</code></a> in <a
href="https://redirect.github.com/caronc/apprise/pull/1665">caronc/apprise#1665</a></li>
<li>Fixed warnings emitted while running test suite in <a
href="https://redirect.github.com/caronc/apprise/pull/1689">caronc/apprise#1689</a></li>
<li>Proxy documentation added to man page in <a
href="https://redirect.github.com/caronc/apprise/pull/1691">caronc/apprise#1691</a></li>
<li>Updated github-action references to latest in <a
href="https://redirect.github.com/caronc/apprise/pull/1692">caronc/apprise#1692</a></li>
<li>Add Italian translation by <a
href="https://github.com/albanobattistella"><code>@​albanobattistella</code></a>
in <a
href="https://redirect.github.com/caronc/apprise/pull/1670">caronc/apprise#1670</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/gaoflow"><code>@​gaoflow</code></a> made
their first contribution in <a
href="https://redirect.github.com/caronc/apprise/pull/1663">caronc/apprise#1663</a></li>
<li><a
href="https://github.com/TheGlenn88"><code>@​TheGlenn88</code></a> made
their first contribution in <a
href="https://redirect.github.com/caronc/apprise/pull/1680">caronc/apprise#1680</a></li>
<li><a href="https://github.com/egvimo"><code>@​egvimo</code></a> made
their first contribution in <a
href="https://redirect.github.com/caronc/apprise/pull/1678">caronc/apprise#1678</a></li>
<li><a href="https://github.com/nyxst4ck"><code>@​nyxst4ck</code></a>
made their first contribution in <a
href="https://redirect.github.com/caronc/apprise/pull/1679">caronc/apprise#1679</a></li>
<li><a href="https://github.com/notrudyyy"><code>@​notrudyyy</code></a>
made their first contribution in <a
href="https://redirect.github.com/caronc/apprise/pull/1674">caronc/apprise#1674</a></li>
<li><a href="https://github.com/hchouhan"><code>@​hchouhan</code></a>
made their first contribution in <a
href="https://redirect.github.com/caronc/apprise/pull/1671">caronc/apprise#1671</a></li>
<li><a
href="https://github.com/IceCodeNew"><code>@​IceCodeNew</code></a> made
their first contribution in <a
href="https://redirect.github.com/caronc/apprise/pull/1684">caronc/apprise#1684</a></li>
<li><a
href="https://github.com/Sanjays2402"><code>@​Sanjays2402</code></a>
made their first contribution in <a
href="https://redirect.github.com/caronc/apprise/pull/1673">caronc/apprise#1673</a></li>
<li><a href="https://github.com/lipusal"><code>@​lipusal</code></a> made
their first contribution in <a
href="https://redirect.github.com/caronc/apprise/pull/1665">caronc/apprise#1665</a></li>
<li><a
href="https://github.com/albanobattistella"><code>@​albanobattistella</code></a>
made their first contribution in <a
href="https://redirect.github.com/caronc/apprise/pull/1670">caronc/apprise#1670</a></li>
<li><a href="https://github.com/youdie006"><code>@​youdie006</code></a>
made their first contribution in <a
href="https://redirect.github.com/caronc/apprise/pull/1694">caronc/apprise#1694</a></li>
<li><a
href="https://github.com/kevinfeyrer"><code>@​kevinfeyrer</code></a>
made their first contribution in <a
href="https://redirect.github.com/caronc/apprise/pull/1696">caronc/apprise#1696</a></li>
</ul>
<h2>Installation</h2>
<p>Apprise is available <a href="https://pypi.org/project/apprise/">on
PyPI</a> through <em>pip</em>:</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/caronc/apprise/commit/cf17bc9ecd2686c62b548b4577b6e06839331a60"><code>cf17bc9</code></a>
bumped version to v1.13.0</li>
<li><a
href="https://github.com/caronc/apprise/commit/c41f802190ff07a71edb98683c2094f04b5b9fc2"><code>c41f802</code></a>
in-memory email attachments no longer fail (<a
href="https://redirect.github.com/caronc/apprise/issues/1696">#1696</a>)</li>
<li><a
href="https://github.com/caronc/apprise/commit/6ebddd3a19a9076d1c0feca410f5e61bf9c927f5"><code>6ebddd3</code></a>
Return None from parse_url() on malformed authority content (<a
href="https://redirect.github.com/caronc/apprise/issues/1694">#1694</a>)</li>
<li><a
href="https://github.com/caronc/apprise/commit/6b8cb1364360c9af61190faa248d0ef4187daaa0"><code>6b8cb13</code></a>
Add Italian translation (<a
href="https://redirect.github.com/caronc/apprise/issues/1670">#1670</a>)</li>
<li><a
href="https://github.com/caronc/apprise/commit/468d26f3b49c4de8dd21a44f1db99288c79247f6"><code>468d26f</code></a>
Updated github-action plugins to latest supported versions (<a
href="https://redirect.github.com/caronc/apprise/issues/1692">#1692</a>)</li>
<li><a
href="https://github.com/caronc/apprise/commit/73c8512a5c518e4c601d6a32c53feabbc2a9e0fa"><code>73c8512</code></a>
Support for Telegram rich message support (<a
href="https://redirect.github.com/caronc/apprise/issues/1690">#1690</a>)</li>
<li><a
href="https://github.com/caronc/apprise/commit/fac5056b8cb50d725f83744ff26636ded60f9f46"><code>fac5056</code></a>
fixed issue causing time based xmpp tests to randomly fail</li>
<li><a
href="https://github.com/caronc/apprise/commit/2c48a87d1bfb5f5afa6a40775f6d3fb8fef2d2e4"><code>2c48a87</code></a>
Proxy documentation added to man page (<a
href="https://redirect.github.com/caronc/apprise/issues/1691">#1691</a>)</li>
<li><a
href="https://github.com/caronc/apprise/commit/094073af543726a52f29672f31ca060f2f8e31b0"><code>094073a</code></a>
relaxed and improved on unit-tests time restrictions</li>
<li><a
href="https://github.com/caronc/apprise/commit/cc2dffb3ccc6a6ee4cf0a6979c9881fe06b43b78"><code>cc2dffb</code></a>
Fixed warnings emitted while running test suite (<a
href="https://redirect.github.com/caronc/apprise/issues/1689">#1689</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/caronc/apprise/compare/v1.12.0...v1.13.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `seleniumbase` from 4.51.12 to 4.52.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.52.1 - Fix timeout issue with executing scripts</h2>
<h2>Fix timeout issue with executing scripts</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/e55bcc7a9f544cbb2ea9ad0c9d4fc358d36c055f">Fix
timeout issue with executing scripts in Selenium Mode</a>
--&gt; (See the next commit where the fix was moved to a
<code>finally</code> block)</li>
<li><strong><a
href="https://github.com/seleniumbase/SeleniumBase/commit/a5e37fd41813e92ed60cb7554781d5ba3aae6ef4">Improve
the earlier commit that fixes the script timeout issue</a></strong>
--&gt; (A method that changed the default timeout wasn't resetting it
back afterward)
--&gt; This resolves <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4467">seleniumbase/SeleniumBase#4467</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/37ace079c94bb9e08d4604387fbce13876ac00fb">Update
examples</a>
--&gt; (Updates some examples after changes were made on the site being
tested)</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>Fix timeout issue with executing scripts by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4468">seleniumbase/SeleniumBase#4468</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.52.0...v4.52.1">https://github.com/seleniumbase/SeleniumBase/compare/v4.52.0...v4.52.1</a></p>
<h2>4.52.0 - CDP Mode: Patch 129</h2>
<h2>CDP Mode: Patch 129</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/814c21ed7d0e94d459778b32db8be1fe7eb85ba5">Update
CDP Mode</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/a3f0b5715fdb2d42e8ca53a043c173555f0ce097">Improve
output from driver downloads</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/17d723fb302ca7eb8a5604c05306a57688494499">Drop
support for Python 3.9</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/0b7cc37597bfdc1e008b6176a8cb793760da534a">Refresh
Python dependencies</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/99327cc58df60a9cce7cd0e50588e806d65e90c3">Add
scraping examples</a></li>
</ul>
<h3>Resolutions:</h3>
<ul>
<li>This resolves <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4461">seleniumbase/SeleniumBase#4461</a></li>
<li>This resolves <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4462">seleniumbase/SeleniumBase#4462</a></li>
<li>This resolves <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4463">seleniumbase/SeleniumBase#4463</a></li>
<li>This resolves <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4464">seleniumbase/SeleniumBase#4464</a></li>
<li>This resolves <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4465">seleniumbase/SeleniumBase#4465</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>CDP Mode: Patch 129 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4466">seleniumbase/SeleniumBase#4466</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.51.12...v4.52.0">https://github.com/seleniumbase/SeleniumBase/compare/v4.51.12...v4.52.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/c71edd581a72bfe46a21c1099a39f2ef8af5aa04"><code>c71edd5</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4468">#4468</a>
from seleniumbase/fix-timeout-issue-with-executing-s...</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/a5e37fd41813e92ed60cb7554781d5ba3aae6ef4"><code>a5e37fd</code></a>
Improve the earlier commit that fixes the script timeout issue</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/cd7b8945ca0298be0f7a8f6578ad4313107ed7c9"><code>cd7b894</code></a>
Version 4.52.1</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/37ace079c94bb9e08d4604387fbce13876ac00fb"><code>37ace07</code></a>
Update examples</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/e55bcc7a9f544cbb2ea9ad0c9d4fc358d36c055f"><code>e55bcc7</code></a>
Fix timeout issue with executing scripts in Selenium mode</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/b2207cb2ff3f0849de5ece7b9e81402983f857d5"><code>b2207cb</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4466">#4466</a>
from seleniumbase/cdp-mode-patch-129</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/0be0f562c918bef4c27ab19c5b3068a28aff8835"><code>0be0f56</code></a>
Version 4.52.0</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/44e9d8564ce6afd764941219916e464174badac3"><code>44e9d85</code></a>
Update the ReadMe</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/99327cc58df60a9cce7cd0e50588e806d65e90c3"><code>99327cc</code></a>
Add scraping examples</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/0b7cc37597bfdc1e008b6176a8cb793760da534a"><code>0b7cc37</code></a>
Refresh Python dependencies</li>
<li>Additional commits viewable in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.51.12...v4.52.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `prek` from 0.4.13 to 0.4.14
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/releases">prek's
releases</a>.</em></p>
<blockquote>
<h2>0.4.14</h2>
<h2>Release Notes</h2>
<p>Released on 2026-08-17.</p>
<h3>Enhancements</h3>
<ul>
<li>Support check-yaml unsafe mode (<a
href="https://redirect.github.com/j178/prek/pull/2546">#2546</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Reuse allocations in common filters (<a
href="https://redirect.github.com/j178/prek/pull/2548">#2548</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Accept non-finite floats in check-yaml (<a
href="https://redirect.github.com/j178/prek/pull/2545">#2545</a>)</li>
<li>Allow comment-heavy YAML in check-yaml (<a
href="https://redirect.github.com/j178/prek/pull/2554">#2554</a>)</li>
<li>Sanitize captured terminal output before replay (<a
href="https://redirect.github.com/j178/prek/pull/2552">#2552</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/j178"><code>@​j178</code></a></li>
</ul>
<h2>Install prek 0.4.14</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://github.com/j178/prek/releases/download/v0.4.14/prek-installer.sh
| sh
</code></pre>
<h3>Install prebuilt binaries via powershell script</h3>
<pre lang="sh"><code>powershell -ExecutionPolicy Bypass -c &quot;irm
https://github.com/j178/prek/releases/download/v0.4.14/prek-installer.ps1
| iex&quot;
</code></pre>
<h3>Install prebuilt binaries via Homebrew</h3>
<pre lang="sh"><code>brew install prek
</code></pre>
<h2>Download prek 0.4.14</h2>
<table>
<thead>
<tr>
<th>File</th>
<th>Platform</th>
<th>Checksum</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.4.14/prek-aarch64-apple-darwin.tar.gz">prek-aarch64-apple-darwin.tar.gz</a></td>
<td>Apple Silicon macOS</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.4.14/prek-aarch64-apple-darwin.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.4.14/prek-x86_64-apple-darwin.tar.gz">prek-x86_64-apple-darwin.tar.gz</a></td>
<td>Intel macOS</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.4.14/prek-x86_64-apple-darwin.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.4.14/prek-aarch64-pc-windows-msvc.zip">prek-aarch64-pc-windows-msvc.zip</a></td>
<td>ARM64 Windows</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.4.14/prek-aarch64-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
</tbody>
</table>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/blob/master/CHANGELOG.md">prek's
changelog</a>.</em></p>
<blockquote>
<h2>0.4.14</h2>
<p>Released on 2026-08-17.</p>
<h3>Enhancements</h3>
<ul>
<li>Support check-yaml unsafe mode (<a
href="https://redirect.github.com/j178/prek/pull/2546">#2546</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Reuse allocations in common filters (<a
href="https://redirect.github.com/j178/prek/pull/2548">#2548</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Accept non-finite floats in check-yaml (<a
href="https://redirect.github.com/j178/prek/pull/2545">#2545</a>)</li>
<li>Allow comment-heavy YAML in check-yaml (<a
href="https://redirect.github.com/j178/prek/pull/2554">#2554</a>)</li>
<li>Sanitize captured terminal output before replay (<a
href="https://redirect.github.com/j178/prek/pull/2552">#2552</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/j178"><code>@​j178</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/j178/prek/commit/432eeb08a4d9fe760817ce8d3d75ed3ebf7d3943"><code>432eeb0</code></a>
Bump version to 0.4.14 (<a
href="https://redirect.github.com/j178/prek/issues/2571">#2571</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/6b6d429319dabac1911b26ce6cb6f04bb814951a"><code>6b6d429</code></a>
Update Rust crate ignore to v0.4.33 (<a
href="https://redirect.github.com/j178/prek/issues/2564">#2564</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/27d0cf8cb309f7b889d2b355254dc96957bbd60c"><code>27d0cf8</code></a>
Update prek hooks (<a
href="https://redirect.github.com/j178/prek/issues/2555">#2555</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/aaf407bb098b624620eac3855e6818b3fa2c4ab0"><code>aaf407b</code></a>
Update Rust crate aws-lc-rs to v1.18.0 (<a
href="https://redirect.github.com/j178/prek/issues/2569">#2569</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/3ddddfd494b87dc0ddee4e7694d1ead3ac4277b7"><code>3ddddfd</code></a>
Update Rust crate xml to v1.4.0 (<a
href="https://redirect.github.com/j178/prek/issues/2570">#2570</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/fc90ec6c8a7b1519691b221bae5224064248a37d"><code>fc90ec6</code></a>
Update Rust crate similar to v3.1.2 (<a
href="https://redirect.github.com/j178/prek/issues/2567">#2567</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/50e60d412350fcef8bc9e50755bd4ea357a45328"><code>50e60d4</code></a>
Update Rust crate thiserror to v2.0.20 (<a
href="https://redirect.github.com/j178/prek/issues/2568">#2568</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/70a4c0635352987f55b995ecd768f1e8a2f9c9db"><code>70a4c06</code></a>
Update Rust crate liblzma to v0.4.8 (<a
href="https://redirect.github.com/j178/prek/issues/2565">#2565</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/7b8dcaa4579449a9a656889398e7d380bd117b08"><code>7b8dcaa</code></a>
Update Rust crate globset to v0.4.20 (<a
href="https://redirect.github.com/j178/prek/issues/2563">#2563</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/3e075d33a5584a8b0f467c39eac3833a8efd4da2"><code>3e075d3</code></a>
Update Rust crate clap_complete to v4.6.9 (<a
href="https://redirect.github.com/j178/prek/issues/2562">#2562</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/j178/prek/compare/v0.4.13...v0.4.14">compare
view</a></li>
</ul>
</details>
<br />

Updates `ruff` from 0.16.3 to 0.16.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/releases">ruff's
releases</a>.</em></p>
<blockquote>
<h2>0.16.4</h2>
<h2>Release Notes</h2>
<p>Released on 2026-08-20.</p>
<h3>Preview features</h3>
<ul>
<li>[<code>flake8-use-pathlib</code>] Add autofix for
<code>PTH116</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26460">#26460</a>)</li>
<li>[<code>refurb</code>] Restrict <code>delete-full-slice</code> to
lists (<code>FURB131</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27711">#27711</a>)</li>
<li>[<code>refurb</code>] Skip <code>FURB101</code> and
<code>FURB103</code> when the <code>open</code> argument is a file
descriptor (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27643">#27643</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Fix <code>InvalidInstruction</code> on Windows CPUs that do not
support <code>POPCNT</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27803">#27803</a>)</li>
<li>[<code>pyflakes</code>] Emit semantic syntax errors in string type
definitions as <code>F722</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27835">#27835</a>)</li>
<li>[<code>pylint</code>] Allow <code>os._exit</code> imports in
<code>import-private-name</code> (<code>PLC2701</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27738">#27738</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[syntax-errors] Align mixed t-string/bytes error message with
CPython 3.14 (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27766">#27766</a>)</li>
<li>[<code>ruff</code>] Add <code>ctypes.LittleEndianStructure</code>
and related types to existing exception (<code>RUF012</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27753">#27753</a>)</li>
<li>[syntax-errors] Detect duplicate keyword arguments (<a
href="https://redirect.github.com/astral-sh/ruff/pull/17804">#17804</a>)</li>
<li>[syntax-errors] Detect parameters declared <code>nonlocal</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27628">#27628</a>)</li>
</ul>
<h3>Server</h3>
<ul>
<li>Offer display-only fixes and mark safe fixes preferred (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27807">#27807</a>)</li>
<li>Support pull diagnostics for notebook cells (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27779">#27779</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Add default indicator to rules table (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27724">#27724</a>)</li>
<li>Fix broken link to Python docs (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27757">#27757</a>)</li>
</ul>
<h3>Other changes</h3>
<ul>
<li>Fix s390x stacker assembly in release builds (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27776">#27776</a>)</li>
<li>Guarantee minimum stack size when parsing a module, standalone
expression, and suites (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25464">#25464</a>)</li>
<li>Reduce configuration deserialization code size (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27924">#27924</a>)</li>
<li>Check packed AST index bounds (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27849">#27849</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/AbhinavMir"><code>@​AbhinavMir</code></a></li>
<li><a
href="https://github.com/eduardorittner"><code>@​eduardorittner</code></a></li>
<li><a href="https://github.com/royb3"><code>@​royb3</code></a></li>
<li><a
href="https://github.com/MichaReiser"><code>@​MichaReiser</code></a></li>
<li><a href="https://github.com/carljm"><code>@​carljm</code></a></li>
<li><a
href="https://github.com/rosstitmarsh"><code>@​rosstitmarsh</code></a></li>
<li><a href="https://github.com/ntBre"><code>@​ntBre</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's
changelog</a>.</em></p>
<blockquote>
<h2>0.16.4</h2>
<p>Released on 2026-08-20.</p>
<h3>Preview features</h3>
<ul>
<li>[<code>flake8-use-pathlib</code>] Add autofix for
<code>PTH116</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26460">#26460</a>)</li>
<li>[<code>refurb</code>] Restrict <code>delete-full-slice</code> to
lists (<code>FURB131</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27711">#27711</a>)</li>
<li>[<code>refurb</code>] Skip <code>FURB101</code> and
<code>FURB103</code> when the <code>open</code> argument is a file
descriptor (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27643">#27643</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Fix <code>InvalidInstruction</code> on Windows CPUs that do not
support <code>POPCNT</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27803">#27803</a>)</li>
<li>[<code>pyflakes</code>] Emit semantic syntax errors in string type
definitions as <code>F722</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27835">#27835</a>)</li>
<li>[<code>pylint</code>] Allow <code>os._exit</code> imports in
<code>import-private-name</code> (<code>PLC2701</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27738">#27738</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[syntax-errors] Align mixed t-string/bytes error message with
CPython 3.14 (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27766">#27766</a>)</li>
<li>[<code>ruff</code>] Add <code>ctypes.LittleEndianStructure</code>
and related types to existing exception (<code>RUF012</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27753">#27753</a>)</li>
<li>[syntax-errors] Detect duplicate keyword arguments (<a
href="https://redirect.github.com/astral-sh/ruff/pull/17804">#17804</a>)</li>
<li>[syntax-errors] Detect parameters declared <code>nonlocal</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27628">#27628</a>)</li>
</ul>
<h3>Server</h3>
<ul>
<li>Offer display-only fixes and mark safe fixes preferred (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27807">#27807</a>)</li>
<li>Support pull diagnostics for notebook cells (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27779">#27779</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Add default indicator to rules table (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27724">#27724</a>)</li>
<li>Fix broken link to Python docs (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27757">#27757</a>)</li>
</ul>
<h3>Other changes</h3>
<ul>
<li>Fix s390x stacker assembly in release builds (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27776">#27776</a>)</li>
<li>Guarantee minimum stack size when parsing a module, standalone
expression, and suites (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25464">#25464</a>)</li>
<li>Reduce configuration deserialization code size (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27924">#27924</a>)</li>
<li>Check packed AST index bounds (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27849">#27849</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/AbhinavMir"><code>@​AbhinavMir</code></a></li>
<li><a
href="https://github.com/eduardorittner"><code>@​eduardorittner</code></a></li>
<li><a href="https://github.com/royb3"><code>@​royb3</code></a></li>
<li><a
href="https://github.com/MichaReiser"><code>@​MichaReiser</code></a></li>
<li><a href="https://github.com/carljm"><code>@​carljm</code></a></li>
<li><a
href="https://github.com/rosstitmarsh"><code>@​rosstitmarsh</code></a></li>
<li><a href="https://github.com/ntBre"><code>@​ntBre</code></a></li>
<li><a
href="https://github.com/zaniebot"><code>@​zaniebot</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ruff/commit/11c76bf48fdac06b2f240cba502eda96da4dce77"><code>11c76bf</code></a>
Bump 0.16.4 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27937">#27937</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/d53c8c58662ca0576ddd502aa1a2979acf03832f"><code>d53c8c5</code></a>
Isolate playground builds from deployment credentials (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27839">#27839</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/cab001e5dec22f55653021f1f7be449e47c7d81e"><code>cab001e</code></a>
Disable uv preview for releases and pre-commit hooks (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27939">#27939</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/f8d575fedc97e75ea62c679d77afb14246afa88e"><code>f8d575f</code></a>
[ty] Clarify writing guidance for human readers (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27912">#27912</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/ca45faebb1750a213df19ed7f686ee5cf9277f93"><code>ca45fae</code></a>
Set <code>--preview</code> and <code>--default-index</code> for the
<code>uv-lock</code> hook (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27935">#27935</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/4827bf7cb449055e46fbfaf4b26e5125883a0569"><code>4827bf7</code></a>
Export <code>UV_DEFAULT_INDEX</code> in <code>release.sh</code> (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27934">#27934</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/d1087a4b9e03d253a88703f34e0869ee4b805456"><code>d1087a4</code></a>
[ty] Handle assignment expressions in string annotations (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27921">#27921</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/680cce48b6d89ab5b1566e4b797bd4847d861815"><code>680cce4</code></a>
[ty] Optimize inherited recursive protocol comparisons (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27922">#27922</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/974d3cbc04520c112843d6b92577844587402e04"><code>974d3cb</code></a>
Upgrade ecosystem-analyzer and mypy_primer to the latest upstream pins
(<a
href="https://redirect.github.com/astral-sh/ruff/issues/27932">#27932</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/b169b402356d0676451f4a7bc6903da2644b31eb"><code>b169b40</code></a>
Install cargo tools locked (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27929">#27929</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ruff/compare/0.16.3...0.16.4">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-24 01:56:46 -04:00
CaliBrain 95e34670f7 fix/group archive extracted audiobooks (#1261)
- fix: group multi-file audiobooks that arrive as an archive
- Surface the concrete reason when a direct-download fetch fails
2026-08-24 01:25:00 -04:00
CaliBrain 7d56624ab6 fix: group multi-file audiobooks that arrive as an archive (#1254)
Follow-up to #1237. \`rename_and_group\` only grouped when the source
root was a directory, so a multi-file audiobook delivered as a single
archive fell through to the flat path: a \`Book.zip\` of twelve chapters
landed loose in the destination root with its original chapter names —
the layout #1181 is about.

The \`is_dir()\` guard was there to keep \`Book.zip/\` from becoming the
folder name, but skipping the file case gives up the grouping instead of
naming it. A non-directory source can only produce several book files by
having been extracted (\`collect_staged_files\` returns a single-element
list for every other file shape), so the archive stem is the release
name and the suffix is packaging: group under \`Book/\`.

Also regenerates the env docs for the new option and gives it the same
\"do not use with ingest folders\" caveat Rename and Organize carries,
since both now create directories in the destination.

Tested: reverting only the source fix makes both new tests fail and the
\`rename\` control case pass, so grouping stays opt-in. Full non-e2e
suite green (2653 passed).
2026-08-21 10:43:06 -04:00
f4421ff189 fix: preserve multi-file audiobook folders (#1237)
Pass the effective source root from `process_folder_output` into
`transfer_book_files`, and have the transfer layer select a sanitized
child directory named after that source root when an audiobook has
multiple files and its organization mode is `none` or `rename`. Create
that grouping directory before applying the existing hardlink/copy/move
logic so operation accounting, torrent seeding preservation, collision
handling, cleanup, and custom-script final paths continue to use the
established production path. Completed multi-file audiobook torrents
arrive as a directory whose chapter filenames may not identify the book,
but folder output currently sends every discovered chapter directly to
the configured destination in `none` and `rename` modes. This flattens
chapters from unrelated books together and causes directory-oriented
consumers such as Audiobookshelf to interpret individual chapters as
separate books.

A multi-file audiobook torrent in the default `rename` mode copies or
hardlinks all supported chapter files beneath `<destination>/<original
torrent directory>/` with their original chapter filenames, and places
no chapters directly in the destination root; A multi-file audiobook in
`none` mode receives the same source-folder grouping without renaming
its chapter files.

Fixes #1181

---------

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: CaliBrain <calibrain@l4n.xyz>
2026-08-21 09:44:08 -04:00
CaliBrain e7007865a4 fix(prowlarr): stop turning indexer failures into empty results and 404s (#1251)
Two independent bugs, both from an indexer that Prowlarr proxies rather
than answers for itself: the search never reported that it had failed,
and the grab never resolved what it was handed.

Search. A Torznab search is Prowlarr proxying a live request out to the
tracker, so for a Cloudflare-fronted indexer it waits on FlareSolverr.
The client gave it the 30s budget sized for Prowlarr's own JSON
endpoints, then swallowed every failure -- the timeout, the 429 Prowlarr
returns once it has disabled an indexer, a parse error -- into the same
empty list that means "this indexer has nothing". A cold challenge
routinely runs past a minute, so the UI said "No releases found for this
book" while FlareSolverr was still solving. That empty list also drove
the auto-expand retry, which fires on "no results with the category
filter". A timeout satisfies it, so Shelfmark sent a second search to an
indexer still busy with the first -- two Chromes at once, enough to take
FlareSolverr's down on a small host.

torznab_search now raises ProwlarrSearchError, and an empty list
strictly
means the indexer answered with no matches. The source records which
indexer searches failed: one dead indexer no longer sinks the others,
auto-expand runs only when every indexer genuinely answered, and zero
results with at least one failure raises SourceUnavailableError, which
the releases endpoint already turns into a 503 carrying a real message.
Prowlarr being unreachable was the same lie by another route -- the
indexer list came back empty, leaving nothing to query -- and now says
so.

Indexer searches also get their own timeout, PROWLARR_INDEXER_TIMEOUT,
defaulting to 90s and clamped to 5-300. Prowlarr's status and indexer
list keep 30s so Test Connection stays responsive, and the connect
timeout is split out at 10s so an unreachable Prowlarr fails fast rather
than hanging for the whole read budget. The overall per-request search
budget now scales to twice the indexer timeout, capped at 240s, so
raising the setting is not undone by the cap one level up while staying
under the 300s gunicorn worker timeout.

Grab. Prowlarr hands out a proxy download URL, with no magnetUrl and no
infoHash, for any indexer that only publishes torrent files. The native
Real-Debrid client built its magnet as "if not
url.startswith('magnet:') and expected_hash", so with no hash to work
from it left the URL alone and POSTed it to /torrents/addMagnet as the
magnet field. Real-Debrid answered 404 and the grab died on a raw HTTP
error. AllDebrid carried the same line and the same bug.

Both now resolve the URL first, through the extract_torrent_info path
the
torrent clients have used since #1108: pass a magnet through untouched,
follow a redirect or a response body that turns out to be a magnet,
otherwise upload the fetched .torrent, and fall back to a magnet built
from the infoHash only when the fetch failed. The file is preferred over
a synthesized urn:btih: magnet because it carries the tracker list; a
bare hash leaves the service to find the swarm on DHT alone. Fetches are
shared with the rest of the add path through the torrent fetch cache, so
resolving costs at most one request. Real-Debrid takes the file on PUT
/torrents/addTorrent with the raw bytes as the request body, AllDebrid
on
POST /magnet/upload/file as multipart files[]. A URL that resolves to
neither form now raises before any request reaches the service, so the
user reads why instead of a 404. Neither debrid client had any test
coverage; both have some now.

Fixes #1249
Fixes #1250
2026-08-21 09:07:03 -04:00
CaliBrain 5b3df2a463 docs(hardcover): list the API key scopes Shelfmark needs (#1243)
Hardcover's August 2026 token system replaced blanket access with
per-token scopes, and nothing in the docs said which ones Shelfmark
actually uses. A key missing write:library or write:lists still passes
Test Connection -- the reading-status and auto-remove-on-download calls
just fail silently afterwards.

Verified against a live hc_pat_ key: every scope in the table backs a
query or mutation the provider really issues, and the omitted ones
(journal, goals, reviews, prompts, notifications, account) are absent
from the provider entirely.

Refs #1240
2026-08-20 19:29:55 -04:00
CaliBrain 5247ec6124 fix(bypass): close the gaps a helper that outlives its request opened (#1244)
assumptions the code around it still made were written for a helper that
was killed after every request.

A bypass that hits the child's deadline is cancelled from the calling
thread, which returns the moment the cancellation is scheduled - so the
helper went on to serve the next request while the abandoned one was
still closing its browser, on the same loop, sharing the DISPLAY globals
and one process group. The deadline now lives inside the loop, where
asyncio.wait_for() waits for the unwind before it raises, with the
calling thread keeping a bounded backstop in case the cleanup wedges
too. Both budgets are set so the child still answers before the parent
gives up on it.

The helper's cookie store survived the request as well, and the whole of
it is exported back to the parent on every answer - so clearance the
parent had purged for one host came back the next time some other host
was solved, the dead-cookie resurrection _redirect_loop_handoff purges
to avoid. The child starts each request from an empty store again; the
parent already runs the cached-cookie check against a superset of it.

DNS config is compared against what the helper is actually resolving
through rather than skipped whenever the parent reports "auto", so a
user flipping CUSTOM_DNS back to auto - which applies live - reaches a
warm helper instead of leaving it on an abandoned DoH resolver.

The 15s exit grace is now asked only of a helper that can still read its
stdin. One dropped mid-bypass never returns to that read, so the grace
could only end in the kill - while a user cancelling a download, and
every bypass queued behind them on LOCKED, waited it out.

Result files are cleaned on the timeout and cancellation paths too,
staging file included, rather than only when the answer was read.
2026-08-20 19:29:33 -04:00
CaliBrain bd21ec1257 fix(audiobookbay): search the ASCII punctuation ABB actually stores (#1242)
WordPress texturizes punctuation on output only, so a post stored as
"The
Stranger's Wife" renders as "The Stranger’s Wife". ABB's search matches
the
stored value and ANDs its terms, so one typographic character in the
query
empties the entire result set rather than merely ranking worse. Book
metadata
and mobile keyboards both hand us those characters.

Map curly quotes, dashes and ellipses to ASCII before a query goes out,
and on
both sides of the relevance comparison, since scraped titles carry the
rendered
forms. Release titles are still stored and displayed exactly as ABB
renders
them; only matching normalizes.

Also percent-encode the search query properly. The hand-rolled encoder
only
escaped double quotes and spaces, so a bare "&" started a new query
parameter
and silently truncated the search: "detective dan riley books 1 & 2
weatherley"
reached ABB as "detective dan riley books 1" and returned six
confident-looking
results without the requested book among them. "%" and "+" were mangled
too.
2026-08-20 19:00:27 -04:00
helgehelge123andhelgehelge123 7b9c416df8 perf(bypass): keep the helper subprocess alive between bypasses (#1222)
Every protected request spawns a fresh helper subprocess, paying
interpreter start and imports before any work begins. Measured inside
the container, five consecutive runs of `python -c "import
shelfmark.bypass.internal_bypasser"`:

```
3.53s  3.45s  3.55s  3.54s  3.46s
```

A single search issues several protected requests, so that is paid
several times over per search.

## What changed

The helper now serves one JSON request per line of stdin until the
parent closes the pipe, and an idle timer
(`BYPASS_BROWSER_IDLE_TIMEOUT`, default 180s) shuts it down once
searching stops.

Answers still travel by result file, but the file is now written to a
`.part` path and renamed into place — the parent treats the file's
existence as the answer, so it must never observe a half-written one.
stdout and stderr stay attached to the parent's, so helper logs keep
appearing in `docker logs` exactly as before.

Failure handling, since a warm helper is exposed to more of it than a
per-request one ever was:

| Situation | Handling |
| --- | --- |
| Helper died between requests | Detected via `poll()`, respawned |
| Pipe broken at write time (`poll()` can miss this) | One retry on a
fresh process; a fresh one failing there is a real failure |
| Helper exits without writing a result | `RuntimeError` naming the exit
code |
| Wedged past the timeout, or cancelled mid-bypass | Helper killed, then
`_cleanup_orphan_processes` because a killed helper never got to close
Chrome |
| Idle reaper racing an arriving request | Re-checks the deadline under
the lock and re-arms instead of killing a helper that just did work |

The DNS config now travels with every request rather than only at spawn:
a warm helper outlives changes the parent makes to its provider.

## `BYPASS_REUSE_BROWSER`, off by default

This parks the CDP driver between bypasses. A driver's websockets are
bound to the loop that opened them and cannot outlive their process, so
the persistent helper is what makes this possible at all — and the warm
path runs on `_CDP_WORKER`'s long-lived loop rather than `asyncio.run`
for the same reason.

The mechanism works. With it on, the browser start disappears from the
second request onward: 0.7s from `Reusing warm Chrome browser` to the
first bypass attempt, against roughly 16s cold.

**It still ships off, because a matched-pair test shows it is a net loss
against DDoS-Guard.** Each round primed with one cold bypass, waited
10s, then measured a second — identical timing in both arms, only the
browser strategy differing, order balanced (fresh, warm, warm, fresh) so
drift over the session cannot masquerade as an effect:

| Arm | Measured request |
| --- | --- |
| fresh browser | 42.8s, 40.6s |
| warm browser | 57.1s, 59.6s |

Spread within each arm is 2.2s and 2.5s, against 16.7s between them.
Reuse removes the ~15s browser start and then gives back roughly twice
that in solving: a returning browser draws a harder challenge. Where the
cold browser is through on the second bypass method, the warm one fails
the first three and only `_bypass_method_humanlike` gets it, at ~30s for
that method alone.

Worth separating from a second effect I ran into while measuring: five
back-to-back searches slow from ~32s to 51–98s with reuse **disabled**
as well, so DDoS-Guard escalates on request rate independently of any of
this. That is why the pairs above are timed identically rather than
simply run in sequence. It is the larger of the two effects, but not
something this project can patch around.

Reuse is left available rather than dropped because Cloudflare sites may
not respond the same way, and because the two concerns are independent:
the helper start is pure overhead and always worth removing, the browser
is not.

## Verification

- 2559 unit tests pass (2542 before, 17 added in
`tests/bypass/test_warm_browser.py`)
- `ruff check`, `ruff format`, `basedpyright` over backend and tests,
and `vulture` all clean
- `docs/environment-variables.md` regenerated via
`scripts/generate_env_docs.py`
- Live against Anna's Archive on a warm helper: searches return their
usual ~760KB and 667 results, the app's own search warm-up completes
with 50 results, and the container is left with no orphan
chrome/Xvfb/ffmpeg processes

Happy to drop the `BYPASS_REUSE_BROWSER` half entirely if you would
rather not carry a default-off path — the helper persistence stands on
its own.

Co-authored-by: helgehelge123 <helge.neumann@zollsoft.de>
2026-08-20 19:00:06 -04:00
CaliBrain 646b531669 fix(hardcover): accept the short hc_pat_ keys Hardcover issues now (#1241)
Hardcover replaced its ~500 char JWTs with short opaque personal access
tokens ("hc_pat_..."), and the connection test rejected anything under
100 chars before a request ever left Shelfmark, so every newly created
key failed with "API key seems too short".

The length floor now applies only to keys without the hc_pat_ prefix; a
prefixed key goes straight to Hardcover, which is the authority on
whether it is valid. Also strip a pasted "bearer " prefix regardless of
casing -- Hardcover's docs tell users to paste the token into an
"authorization" header, so the prefix rides along on the copy, and the
old case-sensitive removeprefix() sent it through as part of the token.
The API key field now names the expected shape.

Note that Hardcover's PAT path currently answers every hc_pat_ token
with a 500, a fabricated one included, while non-PAT tokens still get a
clean 401. So a new key cannot connect yet regardless of this change --
that failure is server-side and not something this code can reach.

Refs #1240
2026-08-20 14:42:45 -04:00
dependabot[bot] eafb965662 build(deps): bump qbittorrent-api from 2026.8.0 to 2026.8.1 in the python-deps group (#1236)
Bumps the python-deps group with 1 update:
[qbittorrent-api](https://github.com/rmartin16/qbittorrent-api).

Updates `qbittorrent-api` from 2026.8.0 to 2026.8.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/rmartin16/qbittorrent-api/releases">qbittorrent-api's
releases</a>.</em></p>
<blockquote>
<h2>release-2026.8.1</h2>
<h2>What's Changed</h2>
<h3>Features</h3>
<ul>
<li>feat: add missing <code>torrents/add</code> and
<code>torrents/reannounce</code> parameters by <a
href="https://github.com/rmartin16"><code>@​rmartin16</code></a> in <a
href="https://redirect.github.com/rmartin16/qbittorrent-api/pull/656">rmartin16/qbittorrent-api#656</a></li>
<li>feat: add endpoints shipped in qBittorrent v5.2.x by <a
href="https://github.com/rmartin16"><code>@​rmartin16</code></a> in <a
href="https://redirect.github.com/rmartin16/qbittorrent-api/pull/658">rmartin16/qbittorrent-api#658</a></li>
</ul>
<h3>Fixes</h3>
<ul>
<li>fix: accept and forward <code>**kwargs</code> on all API methods by
<a href="https://github.com/rmartin16"><code>@​rmartin16</code></a> in
<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/pull/655">rmartin16/qbittorrent-api#655</a></li>
<li>fix: send <code>seedMode</code> for <code>is_skip_checking</code> on
Web API v2.16.0 by <a
href="https://github.com/rmartin16"><code>@​rmartin16</code></a> in <a
href="https://redirect.github.com/rmartin16/qbittorrent-api/pull/654">rmartin16/qbittorrent-api#654</a></li>
<li>docs: correct version annotations by <a
href="https://github.com/rmartin16"><code>@​rmartin16</code></a> in <a
href="https://redirect.github.com/rmartin16/qbittorrent-api/pull/657">rmartin16/qbittorrent-api#657</a></li>
</ul>
<h3>Chores</h3>
<ul>
<li>Bump cryptography from 48.0.1 to 50.0.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/rmartin16/qbittorrent-api/pull/652">rmartin16/qbittorrent-api#652</a></li>
<li>retire codeql by <a
href="https://github.com/rmartin16"><code>@​rmartin16</code></a> in <a
href="https://redirect.github.com/rmartin16/qbittorrent-api/pull/660">rmartin16/qbittorrent-api#660</a></li>
<li>remove xfail for previous python 3.15 issue by <a
href="https://github.com/rmartin16"><code>@​rmartin16</code></a> in <a
href="https://redirect.github.com/rmartin16/qbittorrent-api/pull/638">rmartin16/qbittorrent-api#638</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/rmartin16/qbittorrent-api/compare/v2026.8.0...v2026.8.1">https://github.com/rmartin16/qbittorrent-api/compare/v2026.8.0...v2026.8.1</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/rmartin16/qbittorrent-api/blob/main/CHANGELOG.md">qbittorrent-api's
changelog</a>.</em></p>
<blockquote>
<h3>v2026.8.1 (16 aug 2026)</h3>
<ul>
<li>Add support for <code>app/rotateAPIKey</code> and
<code>app/deleteAPIKey</code> (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/658">#658</a>)</li>
<li>Add support for <code>torrents/SSLParameters</code> and
<code>torrents/setSSLParameters</code> (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/658">#658</a>)</li>
<li>Add support for <code>torrents/fetchMetadata</code>,
<code>torrents/parseMetadata</code>, and
<code>torrents/saveMetadata</code> (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/658">#658</a>)</li>
<li>Add support for <code>torrents/pieceAvailability</code> (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/658">#658</a>)</li>
<li>Add support for <code>clientdata/load</code> and
<code>clientdata/store</code> (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/658">#658</a>)</li>
<li>Add <code>file_priorities</code> and <code>downloader</code> for
<code>torrents/add</code> (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/656">#656</a>)</li>
<li>Add <code>urls</code> for <code>torrents/reannounce</code> (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/656">#656</a>)</li>
<li>Fix <code>is_skip_checking</code> for <code>torrents/add</code>
being ignored by qBittorrent v5.3.0 (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/654">#654</a>)</li>
<li>Fix missing <code>**kwargs</code> for several endpoints (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/655">#655</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/97e5f577df0bcfd4a7986c79e8d21e88ae8e64d0"><code>97e5f57</code></a>
bump to v2026.8.1 (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/661">#661</a>)</li>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/39e05d109dbb427b1e75a33fc96903ee22e3abd3"><code>39e05d1</code></a>
remove xfail for previous python 3.15 issue (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/638">#638</a>)</li>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/0c067fdd356b3c2da0cdfbc1501c3badeb697e22"><code>0c067fd</code></a>
feat: add endpoints shipped in qBittorrent v5.2.x (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/658">#658</a>)</li>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/64f65eacb62d33b54449ef413047779f6d766022"><code>64f65ea</code></a>
fix: send seedMode for is_skip_checking on Web API v2.16.0 (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/654">#654</a>)</li>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/f302ac473a66d3943878bf8491080f3ab800264e"><code>f302ac4</code></a>
docs: correct version annotations (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/657">#657</a>)</li>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/cd9650277f572d26b1c2c09ff8e86a9917cfe435"><code>cd96502</code></a>
feat: add missing torrents/add and torrents/reannounce parameters (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/656">#656</a>)</li>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/7ed08865b11e6f57c7ef0395e8d929c8842de340"><code>7ed0886</code></a>
fix: accept and forward **kwargs on all API methods (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/655">#655</a>)</li>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/0b293e28356e74b3aead64ac3fd1e02e85d5de87"><code>0b293e2</code></a>
retire codeql (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/660">#660</a>)</li>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/fd276e2d918c31d78528acc280fbbd36b354cf88"><code>fd276e2</code></a>
Bump cryptography from 48.0.1 to 50.0.0 (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/652">#652</a>)</li>
<li>See full diff in <a
href="https://github.com/rmartin16/qbittorrent-api/compare/v2026.8.0...v2026.8.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=qbittorrent-api&package-manager=uv&previous-version=2026.8.0&new-version=2026.8.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-20 10:49:39 -04:00
CaliBrain 7193036626 fix(rtorrent): apply the audiobook label to audiobook downloads (#1239)
add_download() picks self._audiobook_label from a content_type kwarg,
but the only call site never passed one, so is_audiobook was always
False and every download got RTORRENT_LABEL. category does not fill
the gap: _get_category_for_task() returns None for rTorrent, which has
no category concept, leaving content_type as its only audiobook signal.

Pass task.content_type through from base_handler, and match it with the
shared is_audiobook() helper instead of == "audiobook".
normalize_content_type()
treats "book (audiobook)" as an audiobook, so the exact-string check
would have mislabeled that value even once it arrived.

The existing rTorrent tests passed content_type straight to the client,
which is why nothing caught the missing wiring; the new handler test
covers the call site itself.

Post-processing was never affected: destination.py reads
task.content_type directly, so files already landed in
DESTINATION_AUDIOBOOK correctly.

Fixes #1235
2026-08-20 10:39:50 -04:00
CaliBrain 12d554a92f fix(download): hand a 503 carrying a challenge to the bypasser (#1238)
503 is in RETRYABLE_CODES, and the bypasser is only ever reached from
the 403
branch and the AA redirect-loop rescues. Once Z-Library re-serves its
DDoS-Guard
interstitial with the same cookie the #1188 handshake already echoed
back, the
request has nothing left to try and spends every attempt on the same
wall.

Gate the handoff on the response body rather than the status, so a
genuine
overloaded-origin 503 keeps its retry path, and on
allow_bypasser_fallback, so
best-effort fetches still fail fast. The challenge indicators move out
of
internal_bypasser into shelfmark/bypass/challenge.py so http.py can use
them
without importing SeleniumBase, which is lazily imported precisely
because it
is optional.

Refs #1233
2026-08-20 10:29:52 -04:00
CaliBrain fae6140c6a fix(bypass): scope browser cleanup to the calling session (#1232)
The orphan sweep ran a container-wide 'pkill -9 -f
chrome|chromium|Xvfb|ffmpeg', so it also matched browsers another bypass
was still driving. Scope it by process group: kill only our own group
and groups whose leader has died. Spawn the helper with
start_new_session so its browser tree is identifiable, tear the whole
group down after every run (a timed-out helper used to leak its Chrome
and Xvfb), and have an orphaned helper take its browser down with it.
Fixes #1231.
2026-08-18 23:24:43 -04:00
dependabot[bot] 63133097e4 build(deps): update httpx[http2] requirement from >=0.27 to >=0.28.1 (#1227)
Updates the requirements on
[httpx[http2]](https://github.com/encode/httpx) to permit the latest
version.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/encode/httpx/releases">httpx[http2]'s
releases</a>.</em></p>
<blockquote>
<h2>Version 0.28.1</h2>
<h2>0.28.1 (6th December, 2024)</h2>
<ul>
<li>Fix SSL case where <code>verify=False</code> together with client
side certificates.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/encode/httpx/blob/master/CHANGELOG.md">httpx[http2]'s
changelog</a>.</em></p>
<blockquote>
<h2>0.28.1 (6th December, 2024)</h2>
<ul>
<li>Fix SSL case where <code>verify=False</code> together with client
side certificates.</li>
</ul>
<h2>0.28.0 (28th November, 2024)</h2>
<p>Be aware that the default <em>JSON request bodies now use a more
compact representation</em>. This is generally considered a prefered
style, tho may require updates to test suites.</p>
<p>The 0.28 release includes a limited set of deprecations...</p>
<p><strong>Deprecations</strong>:</p>
<p>We are working towards a simplified SSL configuration API.</p>
<p><em>For users of the standard <code>verify=True</code> or
<code>verify=False</code> cases, or
<code>verify=&lt;ssl_context&gt;</code> case this should require no
changes. The following cases have been deprecated...</em></p>
<ul>
<li>The <code>verify</code> argument as a string argument is now
deprecated and will raise warnings.</li>
<li>The <code>cert</code> argument is now deprecated and will raise
warnings.</li>
</ul>
<p>Our revised <a
href="https://github.com/encode/httpx/blob/master/docs/advanced/ssl.md">SSL
documentation</a> covers how to implement the same behaviour with a more
constrained API.</p>
<p><strong>The following changes are also included</strong>:</p>
<ul>
<li>The deprecated <code>proxies</code> argument has now been
removed.</li>
<li>The deprecated <code>app</code> argument has now been removed.</li>
<li>JSON request bodies use a compact representation. (<a
href="https://redirect.github.com/encode/httpx/issues/3363">#3363</a>)</li>
<li>Review URL percent escape sets, based on WHATWG spec. (<a
href="https://redirect.github.com/encode/httpx/issues/3371">#3371</a>,
<a
href="https://redirect.github.com/encode/httpx/issues/3373">#3373</a>)</li>
<li>Ensure <code>certifi</code> and <code>httpcore</code> are only
imported if required. (<a
href="https://redirect.github.com/encode/httpx/issues/3377">#3377</a>)</li>
<li>Treat <code>socks5h</code> as a valid proxy scheme. (<a
href="https://redirect.github.com/encode/httpx/issues/3178">#3178</a>)</li>
<li>Cleanup <code>Request()</code> method signature in line with
<code>client.request()</code> and <code>httpx.request()</code>. (<a
href="https://redirect.github.com/encode/httpx/issues/3378">#3378</a>)</li>
<li>Bugfix: When passing <code>params={}</code>, always strictly update
rather than merge with an existing querystring. (<a
href="https://redirect.github.com/encode/httpx/issues/3364">#3364</a>)</li>
</ul>
<h2>0.27.2 (27th August, 2024)</h2>
<h3>Fixed</h3>
<ul>
<li>Reintroduced supposedly-private <code>URLTypes</code> shortcut. (<a
href="https://redirect.github.com/encode/httpx/issues/2673">#2673</a>)</li>
</ul>
<h2>0.27.1 (27th August, 2024)</h2>
<h3>Added</h3>
<ul>
<li>Support for <code>zstd</code> content decoding using the python
<code>zstandard</code> package is added. Installable using
<code>httpx[zstd]</code>. (<a
href="https://redirect.github.com/encode/httpx/issues/3139">#3139</a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Improved error messaging for <code>InvalidURL</code> exceptions. (<a
href="https://redirect.github.com/encode/httpx/issues/3250">#3250</a>)</li>
<li>Fix <code>app</code> type signature in <code>ASGITransport</code>.
(<a
href="https://redirect.github.com/encode/httpx/issues/3109">#3109</a>)</li>
</ul>
<h2>0.27.0 (21st February, 2024)</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/encode/httpx/commit/26d48e0634e6ee9cdc0533996db289ce4b430177"><code>26d48e0</code></a>
Version 0.28.1 (<a
href="https://redirect.github.com/encode/httpx/issues/3445">#3445</a>)</li>
<li><a
href="https://github.com/encode/httpx/commit/89599a9541af14bcf906fc4ed58ccbdf403802ba"><code>89599a9</code></a>
Fix <code>verify=False</code>, <code>cert=...</code> case. (<a
href="https://redirect.github.com/encode/httpx/issues/3442">#3442</a>)</li>
<li><a
href="https://github.com/encode/httpx/commit/8ecb86f0d74ffc52d4663214fae9526bee89358d"><code>8ecb86f</code></a>
Add test for request params behavior changes (<a
href="https://redirect.github.com/encode/httpx/issues/3364">#3364</a>)
(<a
href="https://redirect.github.com/encode/httpx/issues/3440">#3440</a>)</li>
<li><a
href="https://github.com/encode/httpx/commit/0cb7e5a2e736628e2f506d259fcf0d48cd2bde82"><code>0cb7e5a</code></a>
Bump the python-packages group with 11 updates (<a
href="https://redirect.github.com/encode/httpx/issues/3434">#3434</a>)</li>
<li><a
href="https://github.com/encode/httpx/commit/15e21e9ea3cad4f06e22a7e704aabefdf43d2e29"><code>15e21e9</code></a>
Updating deprecated docstring Client() class (<a
href="https://redirect.github.com/encode/httpx/issues/3426">#3426</a>)</li>
<li><a
href="https://github.com/encode/httpx/commit/80960fa31918d7663c3f4c3ad61661cf0e80628f"><code>80960fa</code></a>
Version 0.28.0. (<a
href="https://redirect.github.com/encode/httpx/issues/3419">#3419</a>)</li>
<li><a
href="https://github.com/encode/httpx/commit/a33c87852b8a0dddc65e5f739af1e0a6fca4b91f"><code>a33c878</code></a>
Fix <code>extensions</code> type annotation. (<a
href="https://redirect.github.com/encode/httpx/issues/3380">#3380</a>)</li>
<li><a
href="https://github.com/encode/httpx/commit/ce7e14da27abba6574be9b3ea7cd5990556a9343"><code>ce7e14d</code></a>
Error on verify as str. (<a
href="https://redirect.github.com/encode/httpx/issues/3418">#3418</a>)</li>
<li><a
href="https://github.com/encode/httpx/commit/47f4a96ffaaaa07dca1614409549b5d7a6e7af49"><code>47f4a96</code></a>
Handle empty zstd responses (<a
href="https://redirect.github.com/encode/httpx/issues/3412">#3412</a>)</li>
<li><a
href="https://github.com/encode/httpx/commit/189fc4bcbe5f314128775dec66a616ac9a31ad48"><code>189fc4b</code></a>
Update CHANGELOG.md, fix typo(s) (<a
href="https://redirect.github.com/encode/httpx/issues/3406">#3406</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/encode/httpx/compare/0.27.0...0.28.1">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-18 22:22:06 -04:00
dependabot[bot] 82aeee387e build(deps-dev): bump the python-deps group with 2 updates (#1226)
Bumps the python-deps group with 2 updates:
[basedpyright](https://github.com/detachhead/basedpyright) and
[ruff](https://github.com/astral-sh/ruff).

Updates `basedpyright` from 1.39.9 to 1.39.10
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/detachhead/basedpyright/releases">basedpyright's
releases</a>.</em></p>
<blockquote>
<h2>v1.39.10 (pyright 1.1.412)</h2>
<h2>What's Changed</h2>
<ul>
<li>add <code>allowedUntypedLibraries</code> and
<code>failOnWarnings</code> to <code>pyrightconfig.schema.json</code> by
<a href="https://github.com/DetachHead"><code>@​DetachHead</code></a> in
<a
href="https://redirect.github.com/DetachHead/basedpyright/pull/1851">DetachHead/basedpyright#1851</a></li>
<li>Update pycharm setup instructions by <a
href="https://github.com/charliecloudberry"><code>@​charliecloudberry</code></a>
in <a
href="https://redirect.github.com/DetachHead/basedpyright/pull/1862">DetachHead/basedpyright#1862</a></li>
<li>Merge 1.1.412 by <a
href="https://github.com/DetachHead"><code>@​DetachHead</code></a> in <a
href="https://redirect.github.com/DetachHead/basedpyright/pull/1869">DetachHead/basedpyright#1869</a></li>
<li>fix redundant <code>workspace/configuration</code> request by <a
href="https://github.com/DetachHead"><code>@​DetachHead</code></a> in <a
href="https://redirect.github.com/DetachHead/basedpyright/pull/1847">DetachHead/basedpyright#1847</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/vmphase"><code>@​vmphase</code></a> made
their first contribution in <a
href="https://redirect.github.com/DetachHead/basedpyright/pull/1866">DetachHead/basedpyright#1866</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/DetachHead/basedpyright/compare/v1.39.9...v1.39.10">https://github.com/DetachHead/basedpyright/compare/v1.39.9...v1.39.10</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/6d830bac284253dc6587d35eb026a9a30aee7771"><code>6d830ba</code></a>
1.39.10</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/a34496c27eb0805e4ac39976361526c92d6b3f59"><code>a34496c</code></a>
fix redundant <code>workspace/configuration</code> request</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/b3074fe4dfa981a1928aebc4d9694ae4e5c03fc9"><code>b3074fe</code></a>
fix links in tsp docs</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/979a3fc4d9a86c018a11aeebf17822c468b71ab2"><code>979a3fc</code></a>
add <code>nodejs-wheel</code> back as a dev dependency</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/285225059bae1e3d41b7ff198d8f1d3e07b7141a"><code>2852250</code></a>
ignore <code>mypy_primer/build</code> in bpr</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/5c4427f639c26a4917747ebd5d133ca425e807cc"><code>5c4427f</code></a>
baseline type errors from new upstream python file</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/ae420b5e0cbcad508d93f603282c5e2614d58182"><code>ae420b5</code></a>
try to fix primer</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/0e5c88e47a8ee57ab73b37702a72997976706405"><code>0e5c88e</code></a>
fixes from merge</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/78adf4f989315b4821d30c3b79f0a0aa3732bb07"><code>78adf4f</code></a>
don't support TSP</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/eff463de301edaf268c816a2be618de4c40b6b62"><code>eff463d</code></a>
Merge tag '1.1.412' into merge-1.1.412</li>
<li>Additional commits viewable in <a
href="https://github.com/detachhead/basedpyright/compare/v1.39.9...v1.39.10">compare
view</a></li>
</ul>
</details>
<br />

Updates `ruff` from 0.16.2 to 0.16.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/releases">ruff's
releases</a>.</em></p>
<blockquote>
<h2>0.16.3</h2>
<h2>Release Notes</h2>
<p>Released on 2026-08-13.</p>
<h3>Preview features</h3>
<ul>
<li>[<code>pylint</code>] Fix false negatives on negative numbers
(<code>PLR6104</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27251">#27251</a>)</li>
<li>[<code>pyupgrade</code>] Add rule to replace <code>while 1</code>
with <code>while True</code> (<code>UP048</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27190">#27190</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>[<code>flake8-bandit</code>] Also check keyword arguments
(<code>S602</code>, <code>S603</code>, <code>S607</code>,
<code>S609</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27687">#27687</a>)</li>
<li>[<code>pylint</code>] Allow <code>continue</code> in
<code>finally</code> on Python 3.8 (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27626">#27626</a>)</li>
<li>[<code>pylint</code>] Fix <code>PLE1307</code> false positive with
bools (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27651">#27651</a>)</li>
<li>[<code>pylint</code>] Fix false positives and negatives with
<code>%b</code> format character (<code>PLE1300</code>,
<code>PLE1307</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27560">#27560</a>)</li>
<li>[<code>pylint</code>] Improve handling of concatenated strings
(<code>PLE1300</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27659">#27659</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>numpy</code>] Make <code>np.chararray</code> autofix
backwards-compatible (<code>NPY201</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27527">#27527</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Enable PGO for Linux x86-64 Ruff releases (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27570">#27570</a>)</li>
<li>Enable PGO for Linux ARM64 Ruff releases (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27574">#27574</a>)</li>
<li>Enable PGO for Windows x86-64 Ruff releases (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27573">#27573</a>)</li>
<li>Enable PGO for macOS ARM64 Ruff releases (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27572">#27572</a>)</li>
<li>Reduce <code>Expr</code> size to 64 bytes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27591">#27591</a>)</li>
</ul>
<h3>CLI</h3>
<ul>
<li>Hyperlink rule codes in <code>ruff check --statistics</code> output
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/27646">#27646</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>[<code>ruff</code>] Also suggest <code>asyncio.TaskGroup</code>
(<code>RUF006</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27461">#27461</a>)</li>
</ul>
<h3>Other changes</h3>
<ul>
<li>Use mimalloc v3 (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27586">#27586</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/Andrej730"><code>@​Andrej730</code></a></li>
<li><a
href="https://github.com/alonfaraj"><code>@​alonfaraj</code></a></li>
<li><a
href="https://github.com/romero-deshaw"><code>@​romero-deshaw</code></a></li>
<li><a href="https://github.com/Avasam"><code>@​Avasam</code></a></li>
<li><a href="https://github.com/tjkuson"><code>@​tjkuson</code></a></li>
<li><a
href="https://github.com/charliermarsh"><code>@​charliermarsh</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's
changelog</a>.</em></p>
<blockquote>
<h2>0.16.3</h2>
<p>Released on 2026-08-13.</p>
<h3>Preview features</h3>
<ul>
<li>[<code>pylint</code>] Fix false negatives on negative numbers
(<code>PLR6104</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27251">#27251</a>)</li>
<li>[<code>pyupgrade</code>] Add rule to replace <code>while 1</code>
with <code>while True</code> (<code>UP048</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27190">#27190</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>[<code>flake8-bandit</code>] Also check keyword arguments
(<code>S602</code>, <code>S603</code>, <code>S607</code>,
<code>S609</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27687">#27687</a>)</li>
<li>[<code>pylint</code>] Allow <code>continue</code> in
<code>finally</code> on Python 3.8 (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27626">#27626</a>)</li>
<li>[<code>pylint</code>] Fix <code>PLE1307</code> false positive with
bools (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27651">#27651</a>)</li>
<li>[<code>pylint</code>] Fix false positives and negatives with
<code>%b</code> format character (<code>PLE1300</code>,
<code>PLE1307</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27560">#27560</a>)</li>
<li>[<code>pylint</code>] Improve handling of concatenated strings
(<code>PLE1300</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27659">#27659</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>numpy</code>] Make <code>np.chararray</code> autofix
backwards-compatible (<code>NPY201</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27527">#27527</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Enable PGO for Linux x86-64 Ruff releases (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27570">#27570</a>)</li>
<li>Enable PGO for Linux ARM64 Ruff releases (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27574">#27574</a>)</li>
<li>Enable PGO for Windows x86-64 Ruff releases (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27573">#27573</a>)</li>
<li>Enable PGO for macOS ARM64 Ruff releases (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27572">#27572</a>)</li>
<li>Reduce <code>Expr</code> size to 64 bytes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27591">#27591</a>)</li>
</ul>
<h3>CLI</h3>
<ul>
<li>Hyperlink rule codes in <code>ruff check --statistics</code> output
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/27646">#27646</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>[<code>ruff</code>] Also suggest <code>asyncio.TaskGroup</code>
(<code>RUF006</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27461">#27461</a>)</li>
</ul>
<h3>Other changes</h3>
<ul>
<li>Use mimalloc v3 (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27586">#27586</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/Andrej730"><code>@​Andrej730</code></a></li>
<li><a
href="https://github.com/alonfaraj"><code>@​alonfaraj</code></a></li>
<li><a
href="https://github.com/romero-deshaw"><code>@​romero-deshaw</code></a></li>
<li><a href="https://github.com/Avasam"><code>@​Avasam</code></a></li>
<li><a href="https://github.com/tjkuson"><code>@​tjkuson</code></a></li>
<li><a
href="https://github.com/charliermarsh"><code>@​charliermarsh</code></a></li>
<li><a
href="https://github.com/chirizxc"><code>@​chirizxc</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ruff/commit/b0e47022cfce4f3594aa26d15ea792681430b6f6"><code>b0e4702</code></a>
Bump 0.16.3 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27723">#27723</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/ecdd401fdbc5b0b22e18759c8bd25cda452e8b32"><code>ecdd401</code></a>
[ty] Separate script and uv modules from project metadata (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27720">#27720</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/126352467217bebfa4cb86fd3c4d20820322d9e3"><code>1263524</code></a>
[ty] Simplify display implementations with std::fmt::from_fn (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27718">#27718</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/59196baedf23c9876d1fcf1fa2ae78f80d306f94"><code>59196ba</code></a>
[ty] Unify polarity-aware relation construction (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27707">#27707</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/b8c5e73abe5b15a74fb066e474d30397d1421cfe"><code>b8c5e73</code></a>
[ty] Disable CodSpeed cycle estimation for instrumented benchmarks (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27706">#27706</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/2b0d21094e2a55491bff60c07fd6f8803876cae5"><code>2b0d210</code></a>
[ty] Centralize matched argument relations (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27705">#27705</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/a9130f3381fe137626d22288c0d45f996541ca7e"><code>a9130f3</code></a>
[<code>pyupgrade</code>] Add rule to replace <code>while 1</code> with
<code>while True</code> (<code>while-one</code>, `...</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/c64c7d6dad1e0a4966ce578b2c03af1e8e7673e1"><code>c64c7d6</code></a>
[ty] Model try exception flow with operation checkpoints (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27471">#27471</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/9dea5ef180b3de748b5fe45787056716f235d11a"><code>9dea5ef</code></a>
[ty] Avoid deriving sequents for typevars with concrete bounds (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27587">#27587</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/9798e88de673ec73051980ebd9aeb681161f3c27"><code>9798e88</code></a>
[ty] Preserve enum exhaustiveness with custom <em>missing</em> methods
(<a
href="https://redirect.github.com/astral-sh/ruff/issues/27700">#27700</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ruff/compare/0.16.2...0.16.3">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-18 22:21:52 -04:00
CaliBrain 4cd1091d16 fix(hardcover): send the field count Hardcover's Book search requires (#1224)
Advanced title search, advanced title+author search, and the title
typeahead returned zero results every time, and the sort fallback added
in #1183 blamed the sort value for it.

Hardcover turns the `fields` search parameter into Typesense's
`query_by`
but keeps `num_typos` and `query_by_weights` as fixed-length presets per
query_type. For query_type=Book the preset expects exactly five fields,
so a shorter list is not searched loosely - the whole search is rejected
with a null results body. Confirmed against the live API: 1, 2, 3, 4 and
6 fields are all rejected, only 5 works, and weights must match
one-for-one when sent. Every Book-type list we sent was the wrong length
- the title typeahead and advanced title search sent 2, title+author
sent 3.

- Send BOOK_SEARCH_FIELDS (the full five) for every narrowed Book search
  and express the intent through weights instead. Weights only bias
  ranking - a field weighted 0 still matches - so a title search now
  ranks titles first rather than restricting to them. That is the
  closest behaviour Hardcover still allows, and there is no client-side
  filter to restore the old precision.
- Pin the field and weight counts in tests, since the failure mode is a
  silent zero results rather than an error.

The sort fallback from #1183 also misread these rejections:

- Select the `error` field on every search and log Hardcover's own
  explanation. The reason is only ever in that sibling field, so a
  rejection surfaced as "returned no result body" with nothing to act
  on. Reading it is what made the field-count rule findable.
- Drop `sort` entirely on the retry instead of sending an empty string.
  An empty sort is a value like any other and can be rejected too.
- Arm the 900s sticky window only after the sortless retry succeeds. It
  was armed before the retry and never rolled back, so one rejected
  typeahead disabled sorting process-wide for 15 minutes whatever the
  actual cause.

Verified against the live Hardcover API: advanced title search 0 -> 84
results, title+author 0 -> 139, title typeahead 0 -> 84 with the exact
title top. 2566 unit tests pass; ruff, basedpyright and vulture clean.

Refs #1183. The sort_by regression #1183 was written for is gone from
Hardcover's side - every sort value it rejected, including the one in
the report, is accepted again today. Two plain-search rejections in that
report (fields=None) remain unexplained: they could not be reproduced
under any per_page, page depth, sort value or query shape, and are most
likely transient upstream. They now self-report the reason if they
recur.
2026-08-16 20:55:08 -04:00
CaliBrain 651096ed7b fix(bypass): reuse external bypasser clearance instead of re-solving (#1223)
Direct download was unusable behind an external bypasser (FlareSolverr /
Byparr): every request paid a 403 plus a full solve, and a search that
never ran was reported to the user as "No books found".

Clearance was discarded on the external path. get_cf_cookies_for_domain
and get_cf_user_agent_for_domain returned {} / None whenever
USING_EXTERNAL_BYPASSER was set, and _fetch_via_bypasser read only
solution.response - dropping solution.cookies and solution.userAgent,
which FlareSolverr-compatible services do return. A solve therefore
cleared the one request that paid for it and nothing else, and a file
download - which the solver cannot proxy, being binary - presented no
clearance at all. Diagnosed from a v1.3.9 debug bundle: ~35s in the
bypasser per search, on every search.

- Move the cookie jar out of internal_bypasser into bypass/cookie_store.
  internal_bypasser imports seleniumbase at module scope, which is the
  dependency an external-bypasser deployment is entitled not to have, so
  it cannot host a store the external path depends on.
- Harvest solution.cookies and solution.userAgent after a successful
  solve. The existing filtering applies unchanged, so the per-check
  __ddg8_/__ddg9_/__ddg10_ trio is still dropped and the external path
  cannot reintroduce the ?check=1 loop fixed in ebb833a. The UA matters
  as much as the cookies: Cloudflare ties cf_clearance to the UA that
  solved the challenge.
- Read cookie fields from either shape - CDP objects or JSON mappings.
  Both use the same field names, expires included.
- Point http.py's getters and _purge_clearance at the shared store, so
  either bypasser fills and drains the same jar.
- Give the Docker helper-subprocess handoff explicit export_store /
  import_store rather than reaching into module globals.

An unsolved challenge was also indistinguishable from an empty result.
_looks_like_aa_page() counted the challenge markers as "recognisably
AA", so _fetch_search_table handed a DDoS-Guard interstitial back as a
legitimate no-table response and the user was told their query found
nothing when the search never ran. Split challenge detection out and
raise SearchUnavailableError with the reason instead. The mirror is
still not quarantined - every mirror shares the same protection, so it
is not the mirror's fault.

Verified: 2531 unit tests pass; ruff, basedpyright and vulture clean;
e2e bypasser-external profile passes (5). Its mock FlareSolverr already
returned cookies and userAgent from /v1 - the contract was there,
shelfmark was not reading it.

Refs #1220. Deliberately not "Fixes": this removes the re-solve and
makes a failed solve legible, but if Byparr genuinely cannot clear AA's
current DDoS-Guard, the reporter now gets that as an error rather than a
silent "no books found". The download path may swallow interstitials the
same way; not audited here.
2026-08-16 12:08:45 -04:00
CaliBrain ebb833a82c fix(bypass): discard rejected DDoS-Guard cookies instead of replaying them (#1221)
A cookie that has been rejected was kept and presented again on every
later
request, so a single bad clearance could re-arm the challenge
indefinitely.

Cookie storage:
- Enforce expiry for every stored cookie, not just cf_clearance.
DDoS-Guard
domains have no cf_clearance, so the existing check never fired for them
and
  expired cookies were replayed forever.
- Stop storing the per-check cookies __ddg8_/__ddg9_/__ddg10_ and
ddg_last_challenge. Captured live from Anna's Archive, these carry the
client
IP and the timestamp the check was issued (~40 min), versus ~1 year for
the
  __ddg1_/__ddg2_/__ddgid_ clearance. Replaying an IP-bound token stops
describing the caller as soon as the egress IP changes, which is routine
  behind a VPN.

Failure handling — every path that is rejected while carrying cookies
now
purges them, not just the redirect loop:
- 403 returned while presenting cookies.
- Cached-cookie attempt rejected, whether by status or by redirect loop.
- Factored the purge into _purge_clearance, guarded on a non-empty
hostname
since clear_cf_cookies("") means "every host" and would wipe clearance
for
  sites that are working fine.

Also fix the search warm-up switches shipped inert in v1.3.8:
SEARCH_WARMUP_ENABLED and SEARCH_WARMUP_QUERY are not in the settings
registry, and config.get only consults the environment for keys it
knows, so
both always returned their defaults — the warm-up could not be turned
off or
retargeted. Read os.environ first.

Refs #1220. Deliberately not "Fixes": the reported failure could not be
reproduced on v1.3.8 from a stable IP (the reporter's own queries all
returned
200 on both the pre- and post-change builds), and the new purge paths
did not
fire in live testing because the failures arrive as redirect loops,
which were
already purged. These are correctness fixes with no measured effect on
that
issue. The underlying problem remains that Chrome-obtained cookies never
satisfy DDoS-Guard when replayed by requests, so every search still
re-solves.

Verified: 2542 unit tests pass; ruff, basedpyright and vulture clean;
e2e
platform baseline (10), full (6) and bypasser-external (5) all pass;
five
sequential live searches against Anna's Archive all returned 200 with
zero
"Exceeded 30 redirects".
2026-08-15 17:08:11 -04:00
CaliBrain b7093f4594 Fix log to debug DNS (#1219) 2026-08-15 15:30:29 -04:00
CaliBrain b656f019be feat(download): add DoH wireformat support, mirror quarantine, and search warmup (#1218)
- Add RFC 8484 DNS wireformat codec and HTTP/2 support (httpx) for
Quad9/OpenDNS DoH providers.
- Quarantine dead, parked, or seized mirrors for the session on hard
failure (DNS errors, connection refused, 410/451, parked pages) while
preserving bypass clearance on live mirrors.
- Add background startup search warmup to prime DNS, elect mirrors, and
pre-solve protection challenges to eliminate cold-start search latency.
- Add comprehensive test suites for DoH wireformat, mirror quarantine,
parked domain detection, and search warmup.
2026-08-15 14:17:44 -04:00
CaliBrain 6e96ead519 Fix frontend timeout search (#1217) 2026-08-15 13:44:09 -04:00
CaliBrain 7345f6be1a Fix README and hints for audiobooks (#1215) 2026-08-15 12:19:58 -04:00
CaliBrain 2b8b35bb52 fix(newznab): make indexer book categories configurable (#1214)
Newznab searches hardcoded category 7000 for ebooks and 3030 for
audiobooks,
so indexers using custom IDs returned no results or the wrong ones. Add
NEWZNAB_EBOOK_CATEGORIES and NEWZNAB_AUDIOBOOK_CATEGORIES (tag lists,
defaulting to 7000 and 3030) and resolve the search categories from
config.

Values are parsed leniently — list or comma/whitespace separated,
non-numeric
entries skipped, duplicates dropped — and fall back to the standard IDs
when
empty, so a cleared field can't silently widen the search to every
category.
NEWZNAB_AUTO_EXPAND remains the way to do that on purpose.

Results carrying a custom ID outside the standard 7000-7999 / 3030
ranges were
typed as "other", which routed custom-category audiobooks as ebooks.
Trust the
searched content type when a result carries a category we explicitly
asked for.

Also drop the unused NEWZNAB_BOOKS / NEWZNAB_AUDIOBOOKS constants from
api.py —
a third copy of the same hardcoding.

Closes #1208
2026-08-15 11:48:10 -04:00
FlozeandCaliBrain 58a5b5ed27 fix: sync renamed CWA usernames safely (#1203)
## Summary

- sync an existing CWA-backed user's username when CWA renames it
- keep username collisions safe by assigning a stable `__cwa` alias
instead of overwriting a local account
- allow username updates through `UserDB` and cover
rename/collision/repeat-sync behavior

Fixes #1197.

## Testing

- `uv run ruff check shelfmark tests`
- `uv run ruff format --check shelfmark tests`
- `uv run vulture shelfmark`
- `uv run pytest tests/core/test_cwa_user_sync.py
tests/core/test_user_db.py tests/core/test_admin_users_api.py
tests/core/test_auth_api.py -k "cwa or update_user"` (36 passed)
- `uv run pytest tests/ -x --tb=short -m "not integration and not e2e"
--ignore=tests/config/test_entrypoint_permissions.py -q` (2445 passed, 5
skipped)

The entrypoint permission tests were excluded locally because macOS
ships Bash 3.2, which does not support the `${1,,}` expansion used by
`entrypoint.sh`; the same failure reproduces on an unchanged checkout.
`make python-typecheck` also currently reports the existing
`settings.py:147` callback return-type mismatch on the unchanged base.

Co-authored-by: CaliBrain <calibrain@l4n.xyz>
2026-08-15 11:40:32 -04:00
Sujeito OperatorandCaliBrain 52c1702419 docker: mount uv at build time instead of copying it into every image (#1200)
### What this PR does

`uv` stops being copied into the image and starts being mounted into the
three `RUN`s that
actually use it. The digest pin stays in exactly one place — it moves
from the `COPY` to a
stage declaration:

```dockerfile
FROM ghcr.io/astral-sh/uv:0.11.3@sha256:90bbb3c... AS uv
```
```dockerfile
RUN --mount=type=cache,target=/root/.cache/uv \
    --mount=from=uv,source=/uv,target=/usr/local/bin/uv \
    uv sync --locked --no-default-groups
```

A stage consumed only through `--mount=from=` contributes no layer to
anything published, so
`uv` never lands in `base`. The two `RUN rm -f /usr/bin/uv /usr/bin/uvx`
lines then have nothing
left to delete and go with it.

### Why

The `base` stage copies uv in, and both final stages try to take it back
out:

```dockerfile
# uv is only needed while building the image.
RUN rm -f /usr/bin/uv /usr/bin/uvx
```

That intent is exactly right. **The mechanism can't carry it out**: a
`RUN` adds a layer, it
does not rewrite the layer underneath. The `COPY` layer is still pushed
and still pulled by
everyone. What the `rm` produces is a whiteout on top of it.

### Measured, not assumed

Read off the published images over the registry API — `linux/amd64`,
both built
`2026-08-13T17:53Z`, pinned by digest so these numbers stay reproducible
after tonight's
scheduled rebuild:

```
ghcr.io/calibrain/shelfmark@sha256:9b6041f797cbcc1e5c50ab42bd010a8f747dfaac926080cb6269ddfae99cf820
  layer  COPY /uv /uvx /bin/                        24.3 MB   of 585 MB total   4.1% of the pull
  layer  RUN rm -f /usr/bin/uv /usr/bin/uvx              159 B

ghcr.io/calibrain/shelfmark-lite@sha256:2eae503d791cef685e10135aaaff77077cfb4a31d6911e31216704988ce28b02
  layer  COPY /uv /uvx /bin/                        24.3 MB   of 221 MB total  11.0% of the pull
```

The `rm` layer unpacks to exactly four tar entries:

```
usr/
usr/bin/
usr/bin/.wh.uv       0 bytes
usr/bin/.wh.uvx      0 bytes
```

Two zero-length overlayfs whiteouts. That is the deletion behaving
exactly as specified — and
removing nothing at all from what anyone downloads.

Same thing without the registry API:

```
$ docker manifest inspect ghcr.io/calibrain/shelfmark-lite:latest
```

and look for the ~24 MB layer; or `docker history` on a local build.

### To be clear about what the `rm` does and doesn't do

**It is not useless and I'm not claiming it is.** It removes `uv` from
the flattened filesystem,
which is what the container sees at runtime and what Trivy/Grype scan by
default — so the
"don't ship a stale installer" half of the intent is already working
today, the same way the
`pip` removal above it does. This PR is about the other half: the bytes.
After it, `uv` is
absent from the filesystem *and* absent from the layers, so nothing
regresses.

This is image size, not a vulnerability, and I would not have opened it
as anything else.

### Why this is safe

- **Nothing at runtime can depend on `uv` or `uvx` today**, and that is
read off your own
artifact rather than argued: both are already whiteouted out of both
published images. `uvx`
is never invoked anywhere in the repo — `entrypoint.sh` has no `uv` in
it, and the Makefile's
  `uv run` lines are the host-side dev workflow, outside the image.
- `/usr/local/bin` is already on `PATH` in `python:3.14.7-slim`, and
your `ENV PATH=/app/.venv/bin:$PATH`
prepends rather than replaces, so `uv` resolves the same way it does
now.
- The pin does not move. Same image, same `sha256`, same resolution per
target platform as
`COPY --from=<image>` does today, so the `linux/amd64` and `linux/arm64`
builds each keep
  getting their own `uv`.
- `RUN --mount=` is already used three times in this file, so the
frontend in use supports
  mounts; `from=` is part of the same feature.
- Your `docker-build-check` job builds `shelfmark-lite` on every PR, so
a build is the cheapest
possible review of this change. As a first-time contributor my workflow
runs sit at
`action_required` until someone approves them — approving is enough to
check the whole claim.

### Notes for reviewers

- I have **not** built these images locally. There is no Docker daemon
on the machine I run on.
Every figure above is read from the published images over the registry
API, and my own
  selftest re-reads them live on each run rather than trusting a note.
- I left the `pip` removal in `base` alone. It has the same shape, but
its stated goal — keeping
a stale installer out of what scanners see — is genuinely achieved by
the flattened
filesystem, and `pip` arrives in the `python:slim` base layer where a
Dockerfile change can't
  reach it anyway.
- Written by an automated agent; saying so plainly seemed better than
not.

Signed-off-by: Sujeito Operator <operator@sujeito.org>
Co-authored-by: CaliBrain <calibrain@l4n.xyz>
2026-08-15 11:32:37 -04:00
3e2a7a48d5 fix: clear the DDoS-Guard cookie probe on AA search (#1209)
## Summary

Two failure modes on the same code path, both reported this week: Anna's
Archive `/search` is gated behind a DDoS-Guard cookie probe that the
manual redirect follower can never satisfy.

**#1202 — the cookie is dropped on every hop.** AA URLs set
`allow_redirects = False`, so `html_get_page` follows redirects by hand.
The 302 to `?check=1` carries a `Set-Cookie` (`__ddg*`) that has to come
back on the next request. Because cookies are passed per call and
`requests` keeps no jar across manual hops, it was discarded each time
and the server just re-issued the same redirect until `_MAX_REDIRECTS`
raised `TooManyRedirects`. The file already had the right helper —
`_new_cookies()` — but only the 503 Z-Library handshake branch called
it.

**#1204 — the loop never reaches the bypasser.** `TooManyRedirects`
isn't in `_is_retryable_error` and carries no status code, so the 403
rescue path (`status == _HTTP_STATUS_FORBIDDEN`) never fired and all
attempts repeated the identical failure — ~2.5 min, surfacing as the
misleading "Network restricted or mirrors are blocked".

These interact, which is why #1202's fix alone isn't enough. Requests
merge as `cookies={**handshake_cookies, **cookies}`, so **stale bypasser
cookies override the fresh handshake ones** — once `_cf_cookies` holds
an expired `__ddg*`, the probe can never clear no matter how faithfully
we echo. Hence one search per restart, exactly as #1204 describes.

## Changes

1. Harvest cookies in the same-host redirect branch, the way the 503
branch already does. `_new_cookies()` returns only *new* values, so a
server re-sending an identical cookie yields an empty dict and a genuine
redirect loop still terminates at `_MAX_REDIRECTS`.
2. Treat a redirect loop as a detected challenge: purge the stored
cookies for that host and switch to the bypasser, instead of burning the
retry budget. Gated on `allow_bypasser_fallback` and
`_is_cf_bypass_enabled()`, and skipped when already bypassing, so
AudiobookBay (`allow_bypasser_fallback=False`) and external-bypasser
setups are unaffected.

The broader point in #1204 stands — the fallback would be better gated
on "challenge detected" than on specific status codes, since DDoS-Guard
presents at least three faces (403 js-challenge, 429, and this redirect
loop). This PR fixes the two live exits without that refactor.

## Tests

Two regression tests, both failing before and passing after:

- `test_html_get_page_echoes_cookies_across_same_host_redirects` — the
fake server only returns results if `__ddg2_` comes back on the
`?check=1` hop.
- `test_html_get_page_redirect_loop_purges_cookies_and_bypasses` —
asserts the stored cookies are cleared, the bypasser runs, and the loop
is cut short rather than repeated per attempt.

`ruff check` and `ruff format` clean. `tests/download/` passes except
`test_download_url_ignores_zlib_cookie_refresh_failure`, which fails
identically on unmodified `main` in my environment (no `seleniumbase` —
the `browser` extra isn't installed).

## Verification

Applied on a live v1.3.7 install (Debian LXC, internal CDP bypasser).
Before: every search timed out through 10 retries with
`TooManyRedirects`, zero results. After:

```
http.py:455 - Redirect loop detected; switching to bypasser
internal_bypasser.py:756 - Bypass successful using _bypass_method_cdp_gui_click
internal_bypasser.py:322 - Extracted 9 protection cookies for annas-archive.pk
direct_download.py:1865 - Found 24 releases via ISBN
```

~25 s per search, results render. Note the second search still re-solves
the challenge, since the freshly stored cookies go stale immediately —
the design issue #1204 raises, left for the broader fix.

Fixes #1202
Fixes #1204

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_012Ln3yVj3sWHG2c6T78W1we

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: CaliBrain <calibrain@l4n.xyz>
2026-08-15 11:27:03 -04:00
dependabot[bot] a178541561 Bump the python-deps group with 3 updates (#1205)
Bumps the python-deps group with 3 updates:
[gevent](https://github.com/gevent/gevent),
[seleniumbase](https://github.com/seleniumbase/SeleniumBase) and
[prek](https://github.com/j178/prek).

Updates `gevent` from 26.7.0 to 26.8.0
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/gevent/gevent/commit/4f684105f537eeceb9988adbfe81420a1f28d9a0"><code>4f68410</code></a>
Preparing release 26.8.0</li>
<li><a
href="https://github.com/gevent/gevent/commit/10489c56d3b03caae5be00bc46996a0180ab3d8c"><code>10489c5</code></a>
Merge pull request <a
href="https://redirect.github.com/gevent/gevent/issues/2199">#2199</a>
from florentinl/florentin.labelle/fix/hubless-thread...</li>
<li><a
href="https://github.com/gevent/gevent/commit/d4f5f098c5d6655e9700336ed6f15e924a7fd142"><code>d4f5f09</code></a>
Document the args[0] cross-thread wakeup mechanism</li>
<li><a
href="https://github.com/gevent/gevent/commit/9b049915e8e308e4286f922fd51baf9d92187a88"><code>9b04991</code></a>
Run test_cross_thread_callback_can_run_before_scheduling_returns on all
backends</li>
<li><a
href="https://github.com/gevent/gevent/commit/ce7996d7048d3555be2869cc6172d47e332efb58"><code>ce7996d</code></a>
Address review comments: document cross-thread race, drop unneeded
cpdef</li>
<li><a
href="https://github.com/gevent/gevent/commit/5288a6eec90ca63f9658b44d67f14fbdfe07259d"><code>5288a6e</code></a>
Add missing <a
href="https://github.com/ignores"><code>@​ignores</code></a>_leakcheck
to new cross-thread test</li>
<li><a
href="https://github.com/gevent/gevent/commit/c72eda095d99e82ca0c767afe56baecbc4c131a2"><code>c72eda0</code></a>
Fix cross-thread notifier scheduling race</li>
<li><a
href="https://github.com/gevent/gevent/commit/908b93730ffef5cee0594e4482b3a43b476c43a4"><code>908b937</code></a>
Merge pull request <a
href="https://redirect.github.com/gevent/gevent/issues/2195">#2195</a>
from ddorian/fix-popen-exit-reentrant-close</li>
<li><a
href="https://github.com/gevent/gevent/commit/b58795f8a659e767b0317cd199d68f273b742167"><code>b58795f</code></a>
Merge pull request <a
href="https://redirect.github.com/gevent/gevent/issues/2191">#2191</a>
from ddorian/fix-1865-global-shutdown-lock</li>
<li><a
href="https://github.com/gevent/gevent/commit/ede2e71a859198b96a22f4c683fa210e750406e6"><code>ede2e71</code></a>
Always use 'versionchanged:: NEXT' to let the release machinery fill in
the c...</li>
<li>Additional commits viewable in <a
href="https://github.com/gevent/gevent/compare/26.7.0...26.8.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `seleniumbase` from 4.51.11 to 4.51.12
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.51.12 - CDP Mode: Patch 128</h2>
<h2>CDP Mode: Patch 128</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/2322c11a532a43a00f7224bb02aba30f677457f5">Perform
no-op on duplicate quit() calls to avoid coroutine warnings</a>
--&gt; This resolves <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4458">seleniumbase/SeleniumBase#4458</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/117c9994dce50e134746580f3f73f62ed22449b2">Refresh
Python dependencies</a>
--&gt; <code>setuptools</code> and <code>platformdirs</code></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>CDP Mode: Patch 128 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4459">seleniumbase/SeleniumBase#4459</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.51.11...v4.51.12">https://github.com/seleniumbase/SeleniumBase/compare/v4.51.11...v4.51.12</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/2b54219498fdda1a6c689be543f7777ed6c125a4"><code>2b54219</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4459">#4459</a>
from seleniumbase/cdp-mode-patch-128</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/6774cef0999b82c3b22dcd5a5a896a6299e8a69b"><code>6774cef</code></a>
Version 4.51.12</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/117c9994dce50e134746580f3f73f62ed22449b2"><code>117c999</code></a>
Refresh Python dependencies</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/2322c11a532a43a00f7224bb02aba30f677457f5"><code>2322c11</code></a>
Perform no-op on duplicate quit() calls to avoid coroutine warnings</li>
<li>See full diff in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.51.11...v4.51.12">compare
view</a></li>
</ul>
</details>
<br />

Updates `prek` from 0.4.12 to 0.4.13
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/releases">prek's
releases</a>.</em></p>
<blockquote>
<h2>0.4.13</h2>
<h2>Release Notes</h2>
<p>Released on 2026-08-10.</p>
<h3>Highlights</h3>
<h4>Manage hook tools with mise</h4>
<p>The new <code>language: mise</code> support lets hooks install tools
using
<a href="https://mise.jdx.dev/"><code>mise</code></a> in an isolated
environment:</p>
<pre lang="yaml"><code>repos:
  - repo: local
    hooks:
      - id: golangci-lint
        name: golangci-lint
        language: mise
additional_dependencies: [&quot;aqua:golangci/golangci-lint@2&quot;]
        entry: golangci-lint run --fast-only ./...
        pass_filenames: false
</code></pre>
<h4>Run commands in hook environments</h4>
<p>The new <code>prek exec</code> subcommand can run an explicit command
in a configured
hook's prepared environment. For example, the hook above makes its
managed
binary available to this command:</p>
<pre lang="console"><code>$ prek exec golangci-lint -- golangci-lint
--version
</code></pre>
<h3>Enhancements</h3>
<ul>
<li>Add <code>mise</code> language support (<a
href="https://redirect.github.com/j178/prek/pull/2540">#2540</a>)</li>
<li>Add <code>deny-filename-pattern</code> and
<code>require-filename-pattern</code> hooks (<a
href="https://redirect.github.com/j178/prek/pull/2488">#2488</a>)</li>
<li>Add <code>prek exec</code> for running commands in a hook
environment (<a
href="https://redirect.github.com/j178/prek/pull/2478">#2478</a>)</li>
<li>Add <code>yaml-language-server:</code> comment to YAML sample config
(<a
href="https://redirect.github.com/j178/prek/pull/2486">#2486</a>)</li>
<li>Make <code>prek cache size</code> output terminal-aware (<a
href="https://redirect.github.com/j178/prek/pull/2508">#2508</a>)</li>
<li>Match file regexes against path bytes (<a
href="https://redirect.github.com/j178/prek/pull/2541">#2541</a>)</li>
<li>Show hook aliases in run output (<a
href="https://redirect.github.com/j178/prek/pull/2497">#2497</a>)</li>
<li>Show hook descriptions in run output (<a
href="https://redirect.github.com/j178/prek/pull/2490">#2490</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Avoid env cache scans for skipped hooks (<a
href="https://redirect.github.com/j178/prek/pull/2502">#2502</a>)</li>
<li>Cache Node version queries (<a
href="https://redirect.github.com/j178/prek/pull/2500">#2500</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/blob/master/CHANGELOG.md">prek's
changelog</a>.</em></p>
<blockquote>
<h2>0.4.13</h2>
<p>Released on 2026-08-10.</p>
<h3>Highlights</h3>
<h4>Manage hook tools with mise</h4>
<p>The new <code>language: mise</code> support lets hooks install tools
using
<a href="https://mise.jdx.dev/"><code>mise</code></a> in an isolated
environment:</p>
<pre lang="yaml"><code>repos:
  - repo: local
    hooks:
      - id: golangci-lint
        name: golangci-lint
        language: mise
additional_dependencies: [&quot;aqua:golangci/golangci-lint@2&quot;]
        entry: golangci-lint run --fast-only ./...
        pass_filenames: false
</code></pre>
<h4>Run commands in hook environments</h4>
<p>The new <code>prek exec</code> subcommand can run an explicit command
in a configured
hook's prepared environment. For example, the hook above makes its
managed
binary available to this command:</p>
<pre lang="console"><code>$ prek exec golangci-lint -- golangci-lint
--version
</code></pre>
<h3>Enhancements</h3>
<ul>
<li>Add <code>mise</code> language support (<a
href="https://redirect.github.com/j178/prek/pull/2540">#2540</a>)</li>
<li>Add <code>deny-filename-pattern</code> and
<code>require-filename-pattern</code> hooks (<a
href="https://redirect.github.com/j178/prek/pull/2488">#2488</a>)</li>
<li>Add <code>prek exec</code> for running commands in a hook
environment (<a
href="https://redirect.github.com/j178/prek/pull/2478">#2478</a>)</li>
<li>Add <code>yaml-language-server:</code> comment to YAML sample config
(<a
href="https://redirect.github.com/j178/prek/pull/2486">#2486</a>)</li>
<li>Make <code>prek cache size</code> output terminal-aware (<a
href="https://redirect.github.com/j178/prek/pull/2508">#2508</a>)</li>
<li>Match file regexes against path bytes (<a
href="https://redirect.github.com/j178/prek/pull/2541">#2541</a>)</li>
<li>Show hook aliases in run output (<a
href="https://redirect.github.com/j178/prek/pull/2497">#2497</a>)</li>
<li>Show hook descriptions in run output (<a
href="https://redirect.github.com/j178/prek/pull/2490">#2490</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Avoid env cache scans for skipped hooks (<a
href="https://redirect.github.com/j178/prek/pull/2502">#2502</a>)</li>
<li>Cache Node version queries (<a
href="https://redirect.github.com/j178/prek/pull/2500">#2500</a>)</li>
</ul>
<h3>Bug fixes</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/j178/prek/commit/6204a68bc591773d8a796e1b6c9898cd35aa520d"><code>6204a68</code></a>
Bump version to 0.4.13 (<a
href="https://redirect.github.com/j178/prek/issues/2543">#2543</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/f1c73c5f1780b1d3931d372016c2f9b244be1126"><code>f1c73c5</code></a>
Add mise language support (<a
href="https://redirect.github.com/j178/prek/issues/2540">#2540</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/3f08ed41fb440af8cc9430cda729f6f41a497c2d"><code>3f08ed4</code></a>
Match file regexes against path bytes (<a
href="https://redirect.github.com/j178/prek/issues/2541">#2541</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/6bc5f06f1f090ecf034109ce5b8cc5c773b096d2"><code>6bc5f06</code></a>
Update Rust crate serde-saphyr to v1 (<a
href="https://redirect.github.com/j178/prek/issues/2539">#2539</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/b8c5c69ea3452a77ebc2711ed3b2b8ad011f4791"><code>b8c5c69</code></a>
Update Rust crate fancy-regex to 0.19.0 (<a
href="https://redirect.github.com/j178/prek/issues/2537">#2537</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/2557af921bf5ed6a919385dc18e0177c05530c71"><code>2557af9</code></a>
Update prek hooks (<a
href="https://redirect.github.com/j178/prek/issues/2528">#2528</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/a9587311bc8aa8bf13de7d5d612b93e00f248500"><code>a958731</code></a>
Update Rust crate http to v1.5.0 (<a
href="https://redirect.github.com/j178/prek/issues/2538">#2538</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/fc8b9a45288e005291e1945792543da7a44a2f3a"><code>fc8b9a4</code></a>
Update Rust crate clap to v4.6.5 (<a
href="https://redirect.github.com/j178/prek/issues/2531">#2531</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/3bacaa18ad0b0ea67b15e7ea35d4175ad213bd86"><code>3bacaa1</code></a>
Update dependency uv to v0.12.1 (<a
href="https://redirect.github.com/j178/prek/issues/2536">#2536</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/222fc00edd3fb3b3b4443ea54bacc1e868e6e6a7"><code>222fc00</code></a>
Update Rust crate toml to v1.1.4 (<a
href="https://redirect.github.com/j178/prek/issues/2535">#2535</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/j178/prek/compare/v0.4.12...v0.4.13">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-15 11:16:22 -04:00
dependabot[bot] 78e1f4daba Bump python from 83c1ceb to ce40764 (#1206)
Bumps python from `83c1ceb` to `ce40764`.


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=python&package-manager=docker&previous-version=3.14.7-slim&new-version=3.14.7-slim)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-15 11:16:14 -04:00
dependabot[bot] eeea92280c Bump the npm-deps group in /src/frontend with 3 updates (#1207)
Bumps the npm-deps group in /src/frontend with 3 updates:
[knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip),
[oxfmt](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt) and
[oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint).

Updates `knip` from 6.32.0 to 6.32.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/webpro-nl/knip/releases">knip's
releases</a>.</em></p>
<blockquote>
<h2>Release 6.32.1</h2>
<ul>
<li>Handle referenced config files in their own plugin (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1931">#1931</a>,
close <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1932">#1932</a>)
(982c1d8e28cc62d3cba5ecde6dd8df2740c7c329)</li>
<li>Fix type-check against typescript@5.0.4
(2febefe44a8b39f74158916a2bc73933b4c281ae)</li>
<li>Update sentry snapshot
(0397bddbf809e2b24fe59a4bea8c0258526bb565)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/webpro-nl/knip/commit/437b608ebc1e098506deb60842c6ce079ff6164e"><code>437b608</code></a>
Release knip@6.32.1</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/2febefe44a8b39f74158916a2bc73933b4c281ae"><code>2febefe</code></a>
Fix type-check against typescript@5.0.4</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/982c1d8e28cc62d3cba5ecde6dd8df2740c7c329"><code>982c1d8</code></a>
Handle referenced config files in their own plugin (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1931">#1931</a>,
close <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1932">#1932</a>)</li>
<li>See full diff in <a
href="https://github.com/webpro-nl/knip/commits/knip@6.32.1/packages/knip">compare
view</a></li>
</ul>
</details>
<br />

Updates `oxfmt` from 0.62.0 to 0.63.0
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/blob/main/npm/oxfmt/CHANGELOG.md">oxfmt's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<p>All notable changes to this package will be documented in this
file.</p>
<p>The format is based on <a
href="https://keepachangelog.com/en/1.0.0">Keep a Changelog</a>.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/commit/c42d6397eab5b2d5bb2bd6746c57bc2a9cad21bd"><code>c42d639</code></a>
release(apps): oxlint v1.78.0 &amp;&amp; oxfmt v0.63.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/25473">#25473</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/00f490d7f72d43ec88b1afe28de153e376caf8ed"><code>00f490d</code></a>
refactor(oxfmt,formatter): split <code>sortImports</code> validation and
use type enum (...</li>
<li>See full diff in <a
href="https://github.com/oxc-project/oxc/commits/oxfmt_v0.63.0/npm/oxfmt">compare
view</a></li>
</ul>
</details>
<br />

Updates `oxlint` from 1.77.0 to 1.78.0
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md">oxlint's
changelog</a>.</em></p>
<blockquote>
<h2>[1.78.0] - 2026-08-10</h2>
<h3>🚀 Features</h3>
<ul>
<li>ccb8fe8 linter/jsdoc: Implement <code>no-blank-blocks</code> rule
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25207">#25207</a>)
(Mikhail Baev)</li>
<li>d4a897c linter/eslint: Implement <code>one-var</code> rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24470">#24470</a>)
(Cole Ellison)</li>
<li>5ab9340 linter/jsx-a11y/anchor-has-content: Add options to match
eslint (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24571">#24571</a>)
(Cole Ellison)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>9573937 linter/typescript: Validate <code>ban-ts-comment</code>
description_format (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25320">#25320</a>)
(Mikhail Baev)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/commit/c42d6397eab5b2d5bb2bd6746c57bc2a9cad21bd"><code>c42d639</code></a>
release(apps): oxlint v1.78.0 &amp;&amp; oxfmt v0.63.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25473">#25473</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/ccb8fe89db08123ff2b86d7fb2f39d0dd6c33df7"><code>ccb8fe8</code></a>
feat(linter/jsdoc): implement <code>no-blank-blocks</code> rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25207">#25207</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/9573937df3cc01f29e1c65bc018ce378ec947e0e"><code>9573937</code></a>
fix(linter/typescript): validate <code>ban-ts-comment</code>
description_format (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25320">#25320</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/d4a897ce2290bf853720b4fbf371304bfea2c980"><code>d4a897c</code></a>
feat(linter/eslint): implement <code>one-var</code> rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24470">#24470</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/5ab9340637eff80539bca89a494e162e94569358"><code>5ab9340</code></a>
feat(linter/jsx-a11y/anchor-has-content): add options to match eslint
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24571">#24571</a>)</li>
<li>See full diff in <a
href="https://github.com/oxc-project/oxc/commits/oxlint_v1.78.0/npm/oxlint">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-15 11:16:09 -04:00
CaliBrain 0a5256ecbb fix(download): reconcile the two AA redirect-loop rescues (#1213)
#1210 and #1212 both added a DDoS-Guard `?check=1` rescue, and #1212 was
branched before #1210 landed, so the merged result had two of them with
identical guards. #1212's inline handoff returns before the raise that
#1210's exception handler keys on, so the handler was shadowed and its
stale-cookie purge — the substance of #1210 — never ran. Its regression
test has been failing on main since the merge.

Fold both into one path:

- `_redirect_loop_handoff()` purges the host's stale clearance cookies,
  then bypasses, so the inline AA handoff and the exception handler
  cannot drift apart again.
- The exception handler keeps its own reason to exist: non-AA hosts run
with allow_redirects=True, so `requests` raises the loop itself and the
  manual AA follower never sees it. It now invokes the bypasser directly
  rather than setting a flag and continuing, which was a no-op at
  MAX_RETRY=1 for the same reason the 403 handoff was.
- An unrescuable loop returns empty instead of raising TooManyRedirects
into the retry path. That error is not retryable and carries no status,
  so `/dyn/md5/summary` (allow_bypasser_fallback=False) re-ran the full
  6-redirect loop on all 10 attempts: 60 requests to AA and ~30s of
  backoff, measured. Every AA mirror shares the challenge, so there is
  nothing to rotate to.
- `allow_bypasser_fallback` docs now describe what the flag actually
  gates; the old text predated #1198 and named the wrong callers.
2026-08-15 11:14:13 -04:00
David YoungandD 6d2af0ac28 fix(download): hand AA challenges to the bypasser immediately (#1212)
## Problem

Two defects in `html_get_page`, either of which is enough to make an
Anna's Archive search fail *without the bypasser ever running*. Found
while chasing why AA search returned nothing on v1.3.7 even with
`USE_CF_BYPASS` on and a working bypasser.

### 1. An AA redirect loop is treated as a network fault

AA serves its DDoS-Guard handshake as a same-host redirect loop:
`/search?…` redirects to `/search?…&check=1`, which redirects back,
indefinitely. The manual redirect follower counts those against
`_MAX_REDIRECTS` and raises `TooManyRedirects`:

```python
redirects_followed += 1
if redirects_followed > _MAX_REDIRECTS:
    _raise_too_many_redirects(f"Too many redirects for {current_url}")
```

That lands in the retry path, so every one of the `MAX_RETRY` attempts
re-runs the same 6-redirect loop and the URL is never offered to the
bypasser — which is the only thing that can clear the challenge. With
the default `MAX_RETRY=10` that's ~60 requests to AA per search, all of
which can only fail:

```
Retry 5/10 for https://annas-archive.pk/search?…&check=1: TooManyRedirects
Retry 6/10 for https://annas-archive.pk/search?…&check=1: TooManyRedirects
…
Giving up after 10 attempts: https://annas-archive.pk/search?…&check=1
```

Surfaced to the user as `Unable to reach download source. Network
restricted or mirrors are blocked.`

### 2. Both bypasser handoffs are a no-op at `MAX_RETRY=1`

The existing 403 handoff — and the new redirect one — set a flag and
`continue`:

```python
logger.info("403 detected; switching to bypasser: %s", current_url)
use_bypasser_now = True
continue
```

The branch that acts on `use_bypasser_now` sits at the top of the
**next** retry attempt. With `MAX_RETRY=1` there is no next attempt, so
a 403 simply ends the search and the bypasser never runs. `MAX_RETRY` is
user-configurable down to 1, so this is reachable in normal use.

The redirect handoff had an additional problem: it sits inside the inner
redirect `while`, so a `continue` there re-enters *that* loop rather
than reaching the retry branch at all.

## Change

Both handoffs now invoke the bypasser directly, through a shared
`_run_bypasser()` closure extracted from the existing branch body. No
behaviour change to the bypass itself — same grace handling, same error
reporting, same `finally`.

The redirect handoff also honours `allow_bypasser_fallback`, for the
same reason the 403 path does: callers such as the `/dyn/md5/summary/…`
fetch behind the details modal pass `False` precisely so a best-effort
request fails fast instead of holding the UI open for a minutes-long
browser solve.

## Result

Measured against `/api/releases` for the same book, internal bypasser,
default `MAX_RETRY`:

| | searches returning results |
|---|---|
| before | 4 / 9 |
| after | 3 / 3, then 7 / 7 |

Zero `TooManyRedirects` give-ups after, and the new path is visible in
the logs:

```
redirect loop on https://annas-archive.gl/search?…&check=1; switching to bypasser
Bypass successful using _bypass_method_cdp_gui_click
```

The request volume drop is the other half of the win — a failing search
no longer emits ~60 requests to AA before giving up.

## Notes

- Only `shelfmark/download/http.py` changes; no config or API surface.
- `use_bypasser_now` is still set before each direct call, so the guard
against double-invocation is unchanged.
- Tested with the internal bypasser (seleniumbase). The
external-bypasser path goes through the same `get_bypassed_page()` call
and is unaffected by the control-flow change, though I have not measured
it against DDoS-Guard specifically — in my testing
FlareSolverr-compatible solvers do not clear that challenge regardless.

Co-authored-by: D <d@e>
2026-08-15 11:01:10 -04:00
Zoltán SzabóandKukkerem 056ddd372a Send DDoS-Guard's ?check=1 redirect loop to the bypasser (#1210)
Fixes #1204.

## Problem

#1198 sends a gated AA `/search` to the bypasser when the origin answers
403.
DDoS-Guard has a second response: when the clearance cookies from an
earlier
solve go stale, it serves an endless `?check=1` redirect instead.

`requests` follows that until `_raise_too_many_redirects`, and
`TooManyRedirects` carries no status code, so `status ==
_HTTP_STATUS_FORBIDDEN`
is false and the rescue never runs. All 10 retries re-send the same dead
cookies, then the search fails as `Unable to reach download source.
Network
restricted or mirrors are blocked.`

Direct-download search therefore works once per container start, and
stays dead
after the stored cookie ages out.

v1.3.7 (`sha256:520715f3…`), internal bypasser, mirrors `.gl/.pk/.gd`:

```
17:04:36 internal_bypasser.py:756 - Bypass successful using _bypass_method_cdp_gui_click
...
17:11:39 http.py:483 - Retry 1/10 for https://annas-archive.gl/search?...&check=1:
    TooManyRedirects: Too many redirects
17:12:12 http.py:493 - Giving up after 10 attempts
17:12:12 main.py:2870 - Release search failed for source direct_download:
    Unable to reach download source. Network restricted or mirrors are blocked.
```

The token is short-lived, which is what makes this reachable in normal
use:

```
$ curl -sD - 'https://annas-archive.gl/search?...&check=1'
HTTP/2 403
server: ddos-guard
set-cookie: __ddg8_=…; Expires=Fri, 14-Aug-2026 15:39:38 GMT   # issued 15:19:38, 20 min
```

## Fix

Handle the loop like the 403: drop the domain's stored cookies, then
retry
through the bypasser. The branch sits above the `status ==` ladder
because
`_get_status_code()` returns `None` for this exception.

Cookies are purged only for the internal bypasser; with an external one
`get_cf_cookies_for_domain()` already returns `{}`.

Related but not changed here: `get_cf_cookies_for_domain()` enforces
expiry for
`cf_clearance` only, so `__ddg*` cookies are never evicted on age, which
is why
they go stale. This patch makes the rescue fire whatever the reason the
cookies
stopped working.

## Verification

The regression test drives a real redirect loop through `html_get_page`
(302 to `&check=1`, exception raised by the production path rather than
faked)
and asserts the cookies are purged and the bypasser runs once.

- `pytest tests/download/test_http_bypasser_fallbacks.py`: 8 passed.
`test_download_url_ignores_zlib_cookie_refresh_failure` fails in my
checkout
  on a missing `seleniumbase`, unrelated to this change.
- `ruff check`, `ruff format --check`: clean.
- Running in production since 2026-08-14 on v1.3.7 with only this file
replaced:
six direct-download searches, five served, three books downloaded end to
end,
against one search per container start before. The rescue mid-download:

```
19:12:14 http.py:449 - Redirect loop detected; switching to bypasser:
    https://annas-archive.gl/md5/cb8fba7abae800ddbae1adfb8d7699d9?&check=1
19:12:38 internal_bypasser.py:756 - Bypass successful using _bypass_method_cdp_gui_click
19:14:36 direct_download.py:1142 - Resolved download URL [aa-slow-nowait]: …
19:14:47 orchestrator.py:735 - download finished; starting post-processing
```

## Separate issue this exposes

DDoS-Guard does not accept a solved cookie from plain `requests`
traffic, so
after this patch the rescue runs for nearly every AA URL.
`internal_bypasser.get()`
serializes all solves on one module-wide lock and builds a fresh Chrome
each
time: 11-16 s uncontended, 43-52 s under concurrent load, measured on
the host
above. Correctness is cheap here, latency is not. Happy to open a
separate PR
for a warm browser session if that direction is welcome.

Co-authored-by: Kukkerem <Kukkerem@users.noreply.github.com>
2026-08-15 10:59:41 -04:00
CaliBrain d0e008adde Stop dropping audiobook releases that are not m4b or mp3 (#1199)
An IRC audiobook search returned nothing while OpenBooks, reading the
same @search answer from the same channel, listed results. Three
separate defects were discarding them.

The audiobook format list was maintained by hand in four places and had
drifted. The settings UI offered only m4b/mp3/m4a/zip/rar, and that list
is the only one a user's config can be built from, so flac, opus, ogg,
aac, wav and wma were unreachable everywhere — even though the IRC
parser recognized them, the IRC sorter ranked them (dead code that could
never fire), archive extraction knew them and Prowlarr searched for
them. A FLAC audiobook was invisible in search and, if it arrived
anyway, rejected after download as "format not supported".
AUDIOBOOK_FORMATS and ARCHIVE_FORMATS now live once in core.utils and
every layer derives from them, which also restored the missing .opus in
the post-download scan's trackable extensions.

Widening the default alone would not have reached anyone already
affected: initialize_default_configs() writes field defaults only when a
tab has no config file yet, so an existing install keeps its persisted
m4b/mp3 list forever. migrate_audiobook_formats rewrites a list that
still matches the old default exactly and leaves every other value
alone — re-enabling formats someone had deliberately turned off would be
worse than leaving them narrow.

The IRC parser filtered by file extension alone. Multi-file audiobooks
ship as a .rar or .zip of MP3s, which matched neither SUPPORTED_FORMATS
nor SUPPORTED_AUDIOBOOK_FORMATS, so they fell out of the ebook bucket
and the audiobook bucket both. Results are now classified before the
format filter is applied: an audio extension means audiobook, an ebook
extension means ebook, and for a container — where the extension says
nothing about the contents — the release name decides. An ebook archive
stays out of audiobook results.

RESULT_LINE_REGEX matched \w+ after any dot, so a line carrying no file
extension parsed as format "5mb" out of "::INFO:: 620.5MB", taking the
title and the size down with it and guaranteeing every downstream filter
dropped it. Any decimal size did this. The extension is now matched
against the known formats, so such a line falls through to the simple
pattern and comes back as "unknown", which the rest of the parser
already handles. ALL_RECOGNIZED_FORMATS became an ordered tuple in the
process: it was a set, so which extension won for a line naming two of
them depended on set iteration order and could vary between restarts.

Refs #1129
2026-08-13 13:51:40 -04:00
CaliBrain 03e219eb43 Let the bypasser solve bot challenges on Anna's Archive search (#1198)
Anna's Archive put a DDoS-Guard JS challenge in front of /search: the
homepage still returns 200, but /search and /md5/<id> answer 403 on
every mirror (.gl, .pk, .gd all confirmed). Search fetched both with
allow_bypasser_fallback=False, which rotates mirrors on a 403 instead of
invoking the bypasser, so it walked the whole mirror list, exhausted it,
and surfaced "Unable to reach download source. Network restricted or
mirrors are blocked." as a 503 on every query. Adding mirrors could not
help — they sit behind the same gate — and neither could USE_CF_BYPASS,
since search never reached that branch.
Fetch search and the detail page with allow_bypasser_fallback=True so a
403 hands over to the bypasser, which already detects this challenge
(DDOS_GUARD_INDICATORS matches the live page). Echoing the __ddg cookies
back does not clear it; it needs real JS execution. The download-count
fetch keeps allow_bypasser_fallback=False: it is decoration on the
details modal and not worth holding the modal open for a browser solve.
Fixes #1196
2026-08-13 13:06:11 -04:00
dependabot[bot] 0e8608c427 Bump transmission-rpc from 7.0.11 to 7.0.12 in the python-deps group (#1195)
Bumps the python-deps group with 1 update:
[transmission-rpc](https://github.com/Trim21/transmission-rpc).

Updates `transmission-rpc` from 7.0.11 to 7.0.12
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/Trim21/transmission-rpc/releases">transmission-rpc's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.12</h2>
<h3>✨ New Features</h3>
<ul>
<li><a
href="https://github.com/trim21/transmission-rpc/commit/849cc3b9a357f903242915301fcd190d5ae932a3"><code>849cc3b</code></a>
- add v8 migration deprecations <em>(commit by <a
href="https://github.com/trim21"><code>@​trim21</code></a>)</em></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/trim21/transmission-rpc/commit/d05e9c6c2953d7f334b46df600af4b47d9ea65f5"><code>d05e9c6</code></a>
bump: 7.0.12</li>
<li><a
href="https://github.com/trim21/transmission-rpc/commit/2800e6f0a0a64c31f44935e5122cb7ad3d581dfd"><code>2800e6f</code></a>
ci: pin pyproject schema validation</li>
<li><a
href="https://github.com/trim21/transmission-rpc/commit/1bc5f0d51888f526ada2026409b00228287d2fc8"><code>1bc5f0d</code></a>
ci: run workflows on 7.x</li>
<li><a
href="https://github.com/trim21/transmission-rpc/commit/849cc3b9a357f903242915301fcd190d5ae932a3"><code>849cc3b</code></a>
feat: add v8 migration deprecations</li>
<li>See full diff in <a
href="https://github.com/Trim21/transmission-rpc/compare/v7.0.11...v7.0.12">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=transmission-rpc&package-manager=uv&previous-version=7.0.11&new-version=7.0.12)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-13 00:30:47 -04:00
dependabot[bot] 7f770f54fa Bump python from 3.14.6-slim to 3.14.7-slim (#1190)
Bumps python from 3.14.6-slim to 3.14.7-slim.


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=python&package-manager=docker&previous-version=3.14.6-slim&new-version=3.14.7-slim)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-12 00:21:24 -04:00
dependabot[bot] 0eb8b78dc7 Bump the npm-deps group in /src/frontend with 3 updates (#1191)
Bumps the npm-deps group in /src/frontend with 3 updates:
[@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node),
[knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) and
[vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).

Updates `@types/node` from 26.1.2 to 26.2.0
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node">compare
view</a></li>
</ul>
</details>
<br />

Updates `knip` from 6.31.0 to 6.32.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/webpro-nl/knip/releases">knip's
releases</a>.</em></p>
<blockquote>
<h2>Release 6.32.0</h2>
<ul>
<li>fix: attribute wildcard subpath-import aliases as dependency usage
(<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1918">#1918</a>)
(4890a2ad5317b9e3f0ab844631ad0f0592802c3e) - thanks <a
href="https://github.com/jsmecham"><code>@​jsmecham</code></a>!</li>
<li>Add Borp plugin (5eb9ad4cfb2ccb7770eac49f6109459d30590211)</li>
<li>Add tsd plugin (bd4ae7041f3fc6aef27b027644f80daefc432aca)</li>
<li>Resolve Rollup --config argument as plugin configuration
(8111f97981406f4327d5e6a655c13308a9dc4426)</li>
<li>Resolve babel-jest configFile from Jest transform options
(24794ccecb80fdaecfe4d46a26faff46c30d9015)</li>
<li>Add pre-commit plugin
(ac726b0918cb931a6d538797e342484812a6752e)</li>
<li>Thread script words through resolvers to preserve quoting
(89f9ada6ca81229bf8f7293439998acc5f87d457)</li>
<li>Resolve entries from Mocha script arguments
(353f860f8e32f05f7c87dc5501071befc0e5a293)</li>
<li>Detect Node test runner through c8
(fa44be722aa35c6e8b441cd8cd0f4cf644027c22)</li>
<li>Update dependencies (non-major) (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1922">#1922</a>)
(6b1e6f3cbb1749776e3798f46eb2b99cd95ed56a) - thanks <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot]!</li>
<li>fix(playwright): match config files with .cjs, .cts and .mts
extensions (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1919">#1919</a>)
(1c1073a1817fdb3bcd3dc00eda2b1d858e444156) - thanks <a
href="https://github.com/davidpavlovschi"><code>@​davidpavlovschi</code></a>!</li>
<li>Restart VS Code language server process (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1923">#1923</a>)
(d48eac5a2447567a33e835ce3e1648bf3f4ffb2b)</li>
<li>Classify built-ins from module specifiers (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1925">#1925</a>)
(3528c5d0f808b7b2155f69a1f98493ee0e3e06c1)</li>
<li>Don't turn absolute-path script tokens into entries (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1928">#1928</a>)
(e5608e77b434145c8dd5c2c2872bfb90ae622ce7)</li>
<li>Update sponsors data (025884bb53eede041732723197ac0415a1a50f71)</li>
<li>Update dependencies (d2c0a07c09cea70ab2cae5f97d12da272a6e43e6)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/webpro-nl/knip/commit/f21bcbb653377682dd690fb99b0f02977ac524d2"><code>f21bcbb</code></a>
Release knip@6.32.0</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/d2c0a07c09cea70ab2cae5f97d12da272a6e43e6"><code>d2c0a07</code></a>
Update dependencies</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/e5608e77b434145c8dd5c2c2872bfb90ae622ce7"><code>e5608e7</code></a>
Don't turn absolute-path script tokens into entries (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1928">#1928</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/3528c5d0f808b7b2155f69a1f98493ee0e3e06c1"><code>3528c5d</code></a>
Classify built-ins from module specifiers (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1925">#1925</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/1c1073a1817fdb3bcd3dc00eda2b1d858e444156"><code>1c1073a</code></a>
fix(playwright): match config files with .cjs, .cts and .mts extensions
(<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1919">#1919</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/fa44be722aa35c6e8b441cd8cd0f4cf644027c22"><code>fa44be7</code></a>
Detect Node test runner through c8</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/353f860f8e32f05f7c87dc5501071befc0e5a293"><code>353f860</code></a>
Resolve entries from Mocha script arguments</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/89f9ada6ca81229bf8f7293439998acc5f87d457"><code>89f9ada</code></a>
Thread script words through resolvers to preserve quoting</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/ac726b0918cb931a6d538797e342484812a6752e"><code>ac726b0</code></a>
Add pre-commit plugin</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/24794ccecb80fdaecfe4d46a26faff46c30d9015"><code>24794cc</code></a>
Resolve babel-jest configFile from Jest transform options</li>
<li>Additional commits viewable in <a
href="https://github.com/webpro-nl/knip/commits/knip@6.32.0/packages/knip">compare
view</a></li>
</ul>
</details>
<br />

Updates `vite` from 8.2.0 to 8.2.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/releases">vite's
releases</a>.</em></p>
<blockquote>
<h2>plugin-legacy@8.2.1</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/plugin-legacy@8.2.1/packages/plugin-legacy/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v8.2.1</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v8.2.1/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md">vite's
changelog</a>.</em></p>
<blockquote>
<h2><!-- raw HTML omitted --><a
href="https://github.com/vitejs/vite/compare/v8.2.0...v8.2.1">8.2.1</a>
(2026-08-06)<!-- raw HTML omitted --></h2>
<h3>Bug Fixes</h3>
<ul>
<li><strong>build:</strong> make client chunkImportMap work with
<code>sharedPlugins: true</code> (<a
href="https://redirect.github.com/vitejs/vite/issues/23184">#23184</a>)
(<a
href="https://github.com/vitejs/vite/commit/15f03073c915d6ffb9a1fda447ef66b02bf5cde8">15f0307</a>)</li>
<li><strong>bundled-dev:</strong> inject client script tag before chunk
scripts (<a
href="https://redirect.github.com/vitejs/vite/issues/23161">#23161</a>)
(<a
href="https://github.com/vitejs/vite/commit/eac0cc84aa2472a85a19ee84561c1ba71e381a55">eac0cc8</a>)</li>
<li><strong>css:</strong> don't re-run lightningcss visitor during
minify (fix <a
href="https://redirect.github.com/vitejs/vite/issues/23146">#23146</a>)
(<a
href="https://redirect.github.com/vitejs/vite/issues/23147">#23147</a>)
(<a
href="https://github.com/vitejs/vite/commit/de041a79b05a0be965c874592fe2c1505bcd48df">de041a7</a>)</li>
<li><strong>deps:</strong> update all non-major dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/23136">#23136</a>)
(<a
href="https://github.com/vitejs/vite/commit/14454fd8c9a399bc3fdc193e28465b6fcf001e4d">14454fd</a>)</li>
<li><strong>deps:</strong> update rolldown-related dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/23070">#23070</a>)
(<a
href="https://github.com/vitejs/vite/commit/7ac6f7f590747bbdab9958e2c016e3dd04f10542">7ac6f7f</a>)</li>
<li>don't mutate the user config when resolving the lib entry from the
top-level <code>input</code> (<a
href="https://redirect.github.com/vitejs/vite/issues/23135">#23135</a>)
(<a
href="https://github.com/vitejs/vite/commit/b4bf59686a7ac238929e91a6e1708c739b843a2f">b4bf596</a>)</li>
<li>handle shebang ending with uncommon line terminators (<a
href="https://redirect.github.com/vitejs/vite/issues/23038">#23038</a>)
(<a
href="https://github.com/vitejs/vite/commit/17f7b2f193a110d0b47742ad296d182cb4666ce7">17f7b2f</a>)</li>
<li><strong>server:</strong> use a random port when port is 0 (<a
href="https://redirect.github.com/vitejs/vite/issues/23158">#23158</a>)
(<a
href="https://github.com/vitejs/vite/commit/fddf4ea41de5f7889037a2f957438857ac12a260">fddf4ea</a>)</li>
</ul>
<h3>Performance Improvements</h3>
<ul>
<li><strong>css:</strong> look up pure CSS chunks through a Set (<a
href="https://redirect.github.com/vitejs/vite/issues/23114">#23114</a>)
(<a
href="https://github.com/vitejs/vite/commit/1331b0b438b1e7193effb7d2341660bccb9c3155">1331b0b</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li><strong>build:</strong> fix incomplete <code>@default</code> for
build.minify (<a
href="https://redirect.github.com/vitejs/vite/issues/23177">#23177</a>)
(<a
href="https://github.com/vitejs/vite/commit/ef02435114c57d0422028f0e6987f3df8db72969">ef02435</a>)</li>
</ul>
<h3>Miscellaneous Chores</h3>
<ul>
<li><strong>deps:</strong> update dependency rolldown-plugin-dts to
^0.28.0 (<a
href="https://redirect.github.com/vitejs/vite/issues/23137">#23137</a>)
(<a
href="https://github.com/vitejs/vite/commit/4adc1e7931d4beceb4e236d9a271d057c858a06f">4adc1e7</a>)</li>
<li><strong>deps:</strong> update dependency strip-literal to v4 (<a
href="https://redirect.github.com/vitejs/vite/issues/23140">#23140</a>)
(<a
href="https://github.com/vitejs/vite/commit/9db65ce63488ea8f08a3c98dcdc4282b17bd33ff">9db65ce</a>)</li>
</ul>
<h3>Code Refactoring</h3>
<ul>
<li><strong>bundled-dev:</strong> avoid injecting server values in the
bundle (<a
href="https://redirect.github.com/vitejs/vite/issues/22967">#22967</a>)
(<a
href="https://github.com/vitejs/vite/commit/23b8a088dec9dcc3f1c1353f2074f8644b3cc21f">23b8a08</a>)</li>
<li><strong>bundled-dev:</strong> remove rolldown lazy stub module
workaround (<a
href="https://redirect.github.com/vitejs/vite/issues/23129">#23129</a>)
(<a
href="https://github.com/vitejs/vite/commit/e72036eed2e28936ed824971b18aeaa3900857f6">e72036e</a>)</li>
</ul>
<h3>Tests</h3>
<ul>
<li><strong>bundled-dev:</strong> enable sourcemap playgrounds (<a
href="https://redirect.github.com/vitejs/vite/issues/23080">#23080</a>)
(<a
href="https://github.com/vitejs/vite/commit/c2155fe4d5c8d25fba3a7366d367e3296ae669fa">c2155fe</a>)</li>
<li>reduce logs (<a
href="https://redirect.github.com/vitejs/vite/issues/23138">#23138</a>)
(<a
href="https://github.com/vitejs/vite/commit/7673c02e53343ae9356c1f496c1c1da2eb732ac1">7673c02</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/vitejs/vite/commit/421615865dad3ed39137d17281814fc78a41246c"><code>4216158</code></a>
release: v8.2.1</li>
<li><a
href="https://github.com/vitejs/vite/commit/fddf4ea41de5f7889037a2f957438857ac12a260"><code>fddf4ea</code></a>
fix(server): use a random port when port is 0 (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23158">#23158</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/de041a79b05a0be965c874592fe2c1505bcd48df"><code>de041a7</code></a>
fix(css): don't re-run lightningcss visitor during minify (fix <a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23146">#23146</a>)
(<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23147">#23147</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/15f03073c915d6ffb9a1fda447ef66b02bf5cde8"><code>15f0307</code></a>
fix(build): make client chunkImportMap work with <code>sharedPlugins:
true</code> (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23184">#23184</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/c2155fe4d5c8d25fba3a7366d367e3296ae669fa"><code>c2155fe</code></a>
test(bundled-dev): enable sourcemap playgrounds (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23080">#23080</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/ef02435114c57d0422028f0e6987f3df8db72969"><code>ef02435</code></a>
docs(build): fix incomplete <code>@default</code> for build.minify (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23177">#23177</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/eac0cc84aa2472a85a19ee84561c1ba71e381a55"><code>eac0cc8</code></a>
fix(bundled-dev): inject client script tag before chunk scripts (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23161">#23161</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/23b8a088dec9dcc3f1c1353f2074f8644b3cc21f"><code>23b8a08</code></a>
refactor(bundled-dev): avoid injecting server values in the bundle (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22967">#22967</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/e72036eed2e28936ed824971b18aeaa3900857f6"><code>e72036e</code></a>
refactor(bundled-dev): remove rolldown lazy stub module workaround (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23129">#23129</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/14454fd8c9a399bc3fdc193e28465b6fcf001e4d"><code>14454fd</code></a>
fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23136">#23136</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/vitejs/vite/commits/v8.2.1/packages/vite">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-12 00:14:04 -04:00
dependabot[bot] eb98b6a816 Bump the gh-actions group with 2 updates (#1192)
Bumps the gh-actions group with 2 updates:
[actions/attest-build-provenance](https://github.com/actions/attest-build-provenance)
and [dorny/paths-filter](https://github.com/dorny/paths-filter).

Updates `actions/attest-build-provenance` from 4.1.1 to 4.2.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/attest-build-provenance/releases">actions/attest-build-provenance's
releases</a>.</em></p>
<blockquote>
<h2>v4.2.2</h2>
<blockquote>
<p>[!NOTE]
As of version 4, <code>actions/attest-build-provenance</code> is simply
a wrapper on top of <a
href="https://github.com/actions/attest"><code>actions/attest</code></a>.</p>
<p>Existing applications may continue to use the
<code>attest-build-provenance</code> action, but new implementations
should use <code>actions/attest</code> instead.</p>
</blockquote>
<h2>What's Changed</h2>
<ul>
<li>Bump actions/attest from 4.2.0 to 4.2.1 in the actions-minor group
by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/attest-build-provenance/pull/862">actions/attest-build-provenance#862</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/attest-build-provenance/compare/v4.1.1...v4.2.2">https://github.com/actions/attest-build-provenance/compare/v4.1.1...v4.2.2</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/attest-build-provenance/commit/4d101475d8b20a2381f78447822ac1eab6504dd8"><code>4d10147</code></a>
Bump actions/attest from 4.2.0 to 4.2.1 in the actions-minor group (<a
href="https://redirect.github.com/actions/attest-build-provenance/issues/862">#862</a>)</li>
<li><a
href="https://github.com/actions/attest-build-provenance/commit/e3fe62ef559997059fe8380e7d2b4c909e2d65f4"><code>e3fe62e</code></a>
Bump the actions-minor group with 2 updates (<a
href="https://redirect.github.com/actions/attest-build-provenance/issues/860">#860</a>)</li>
<li>See full diff in <a
href="https://github.com/actions/attest-build-provenance/compare/0f67c3f4856b2e3261c31976d6725780e5e4c373...4d101475d8b20a2381f78447822ac1eab6504dd8">compare
view</a></li>
</ul>
</details>
<br />

Updates `dorny/paths-filter` from 4.0.2 to 4.0.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/dorny/paths-filter/releases">dorny/paths-filter's
releases</a>.</em></p>
<blockquote>
<h2>v4.0.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Update Outputs in readme to account for the 'every'
predicate-quantifier by <a
href="https://github.com/hintron"><code>@​hintron</code></a> in <a
href="https://redirect.github.com/dorny/paths-filter/pull/247">dorny/paths-filter#247</a></li>
<li>fix: scope base-ignored warning to API path by <a
href="https://github.com/saschabratton"><code>@​saschabratton</code></a>
in <a
href="https://redirect.github.com/dorny/paths-filter/pull/319">dorny/paths-filter#319</a></li>
<li>docs: add contents permission to PR example by <a
href="https://github.com/134130"><code>@​134130</code></a> in <a
href="https://redirect.github.com/dorny/paths-filter/pull/248">dorny/paths-filter#248</a></li>
<li>feat: add 'some-with-excludes' predicate quantifier by <a
href="https://github.com/arxeiss"><code>@​arxeiss</code></a> in <a
href="https://redirect.github.com/dorny/paths-filter/pull/322">dorny/paths-filter#322</a></li>
<li>Document safe handling of file list outputs in workflows by <a
href="https://github.com/dorny"><code>@​dorny</code></a> in <a
href="https://redirect.github.com/dorny/paths-filter/pull/326">dorny/paths-filter#326</a></li>
</ul>
<h2>Security</h2>
<ul>
<li>Escape multi-line filenames in list-files shell and csv output] by
<a href="https://github.com/ken-matsui"><code>@​ken-matsui</code></a>
and <a href="https://github.com/tjswlsgg"><code>@​tjswlsgg</code></a> in
<a
href="https://github.com/advisories/GHSA-7hc6-8hq5-9q2m">https://github.com/advisories/GHSA-7hc6-8hq5-9q2m</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/hintron"><code>@​hintron</code></a> made
their first contribution in <a
href="https://redirect.github.com/dorny/paths-filter/pull/247">dorny/paths-filter#247</a></li>
<li><a href="https://github.com/134130"><code>@​134130</code></a> made
their first contribution in <a
href="https://redirect.github.com/dorny/paths-filter/pull/248">dorny/paths-filter#248</a></li>
<li><a href="https://github.com/arxeiss"><code>@​arxeiss</code></a> made
their first contribution in <a
href="https://redirect.github.com/dorny/paths-filter/pull/322">dorny/paths-filter#322</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/dorny/paths-filter/compare/v4...v4.0.3">https://github.com/dorny/paths-filter/compare/v4...v4.0.3</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md">dorny/paths-filter's
changelog</a>.</em></p>
<blockquote>
<h2>v4.0.3</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/326">Document
safe handling of file list outputs in workflows</a></li>
<li><a href="https://github.com/advisories/GHSA-7hc6-8hq5-9q2m">Escape
multi-line filenames in list-files shell and csv output</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/322">Add
'some-with-excludes' predicate quantifier</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/248">Add
contents permission to PR example</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/319">Scope
base-ignored warning to API path</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/247">Update
outputs in readme to account for the 'every'
predicate-quantifier</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/dorny/paths-filter/commit/ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d"><code>ceb8a2b</code></a>
Update CHANGELOG.md for v4.0.3 and v3.0.4 (<a
href="https://redirect.github.com/dorny/paths-filter/issues/327">#327</a>)</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/ef09b88f3eacdbec6ce135a7c9a193a6849545c1"><code>ef09b88</code></a>
Document safe handling of file list outputs in workflows (<a
href="https://redirect.github.com/dorny/paths-filter/issues/326">#326</a>)</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/44adc5b06dc135dba334efce9bf3cf0624512d2d"><code>44adc5b</code></a>
Merge commit from fork</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/4711b7a31b4aa89103d8c6ffab2e3b8e7b6381c7"><code>4711b7a</code></a>
feat: add 'some-with-excludes' predicate quantifier (<a
href="https://redirect.github.com/dorny/paths-filter/issues/322">#322</a>)</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/93c889f9e58fca66f35a0c83d8673ac7e88bb70a"><code>93c889f</code></a>
fix: escape multi-line filenames in list-files shell and csv output</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/b41dfa943b1939b9b646f67753bfe35cf6e4de03"><code>b41dfa9</code></a>
docs: add contents permission to PR example (<a
href="https://redirect.github.com/dorny/paths-filter/issues/248">#248</a>)</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/9af6e5a9d010d1ae8ec570390b3d793e2b70a402"><code>9af6e5a</code></a>
fix: scope base-ignored warning to API path (<a
href="https://redirect.github.com/dorny/paths-filter/issues/319">#319</a>)</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/cae9006b65a1a53044b518c68e13e835c54948a7"><code>cae9006</code></a>
docs: update outputs in readme to account for the 'every'
predicate-quantifie...</li>
<li>See full diff in <a
href="https://github.com/dorny/paths-filter/compare/v4.0.2...v4.0.3">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-12 00:13:45 -04:00
CaliBrain 29ce83e274 Stop dependabot proposing Python pre-releases, bound the e2e health wait (#1189)
PR #1169 (python:3.14.6-slim -> python:3.15.0b3-slim) ran for 6h before
GitHub's max job limit killed it, then did it again on re-run. Two
independent defects.

Dependabot proposed a beta at all: the config already excluded python
from the docker digest group for dependabot-core#9496, but the comment
claimed ungrouped python updates get their pre-release filtered. They
don't. dependabot-core#13815 rewrote the Docker pre-release heuristic to
catch PEP 440 tags (its tests cover 3.15.0a2 and 3.5.0b3), yet the
suffixed real tag still got through seven months later. CPython spells
pre-releases without a separator, so 3.15.0b3 parses as an ordinary
version sorting above 3.14.6. Ignore python semver-minor/major instead
of trusting the heuristic; patch and digest updates still flow.

The run took hours rather than failing: the health wait looked bounded
at 60 iterations x 2s, but bare `curl` has no timeout. The 3.15 image
booted a container that bound 8084 without ever serving (greenlet has no
3.15 wheel, so the gevent gunicorn worker was wedged), so curl blocked
on
read forever and the loop never reached iteration 2. Every job's orphan
process at cancellation was that curl. Bound each probe and switch to a
wall-clock deadline, and add timeout-minutes so a hang can never reach
6h
again.

Verified against a socket that accepts and never responds: the old loop
was still hung at 30s, the new one exits at 120s with HEALTHY=0 into the
existing log-dump path, and a responsive endpoint is still detected
immediately.
2026-08-11 23:58:37 -04:00
CaliBrain e320b7623d Fix LOG_LEVEL being ignored and Z-Library 503 cookie gate (#1188)
LOG_LEVEL never reached the app logger: env.py hardcoded the level to
DEBUG or INFO, so INFO lines kept appearing under LOG_LEVEL=error. Read
it from the env var and advanced settings, normalize unknown values to
INFO, and expose it as a setting. entrypoint.sh now normalizes
gunicorn's level too, so a typo falls back to info instead of stopping
the container from booting.

Z-Library gates the first hit on /md5/<hash> with a 503 whose only
payload is a Set-Cookie; echoing that cookie back returns the 302 to the
real page. html_get_page dropped it and re-ran the same rejected request
on every retry, ending in "No download URL resolved". Retry once with
the
cookies the 503 issued.

Fixes #1185
Fixes #1187
2026-08-11 23:27:44 -04:00
CaliBrain bb848f05bc fix/bypass stall watchdog (#1186)
- Fix protection bypass cancelled by stall detection at exactly 300s
- make fixes
- Try to fix Synology DELETE issues
2026-08-11 12:23:34 -04:00
CaliBrain cc1a95f965 Fix protection bypass cancelled by stall detection at exactly 300s (#1184)
A download that hits Cloudflare hung on "Bypassing protection..." for
five
minutes and then died, regardless of which bypasser was configured.

html_get_page() started a BypassHeartbeat thread to keep the download
marked
alive during a bypass, but the thread had no loop: it fired one status
event
and returned. Even with the loop restored it could not have worked,
because
update_download_status() dedupes identical (status, message) tuples and
returns before refreshing _last_activity, and the heartbeat re-sent the
byte-identical payload already emitted just above it.

So _last_activity was frozen for the whole bypass, while both bypassers
are
allowed to run longer than STALL_TIMEOUT (external FlareSolverr ~394s at
default settings, internal 420s per get() call). The watchdog always
won.
From a reporter's log: 403 at 07:04:33.390, cancelled at 07:09:33.987 -
exactly 300.000s, and 41s before the bypasser would have finished and
reported the real error, an HTTP 500 from FlareSolverr the user never
saw.

The regression is not one commit. 1f093de (#536) added the heartbeat and
the
dedup together and refreshed activity before the dedup return, so it
worked.
ff094be (#832) moved the refresh below that return while tightening
stall
detection for #823. 3a3a3ce (#845) then deleted the heartbeat's while
loop
to silence a B023 lint, removing the last evidence of intent.

The dedup itself is correct and stays: a keep-alive that ticks on a
timer
proves nothing about whether an operation is progressing, so letting it
refresh the stall clock would make a wedged download immortal. Split the
two
concerns instead.

Add shelfmark/download/activity.py. A long single-shot operation
declares its
own upper bound once, over a sentinel status carried on the existing
status_callback channel - so no new parameter has to be threaded through
every
handler, post-processor and output module. The orchestrator intercepts
the
sentinel in its per-task closure and records an absolute deadline in
_activity_grace, which stall detection honours alongside STALL_TIMEOUT.
The
grace never extends itself and is clamped to
_MAX_ACTIVITY_GRACE_SECONDS, so
an operation that overruns its own declared budget is still cancelled.

Each bypasser now reports max_duration_seconds() derived from its own
retry
and timeout settings, and http.py asks whichever is active, plus 30s of
slack
so the bypasser's own deadline expires first and the user sees its real
failure. On that path html_get_page() also emits
status_callback("error", ...)
rather than silently returning an empty page.

Three further fixes on the same code path:

- Extract the watchdog into _find_stalled_tasks() and
_cancel_stalled_task().
It was the only place holding _progress_lock across a call into
book_queue,
whose terminal-status hooks reach a sqlite write that gevent does not
patch,
blocking the hub and every download worker. It now holds the lock for
dict
  reads only.
- Bound _CDP_WORKER.run(), which waited with timeout=None while holding
the
module-wide LOCKED, so a single wedged in-process CDP session blocked
every
  subsequent bypass forever on non-Docker installs.
- Broaden the coordinator loop's except clause back to Exception, with
  escalating backoff. 8d98e12 (#868) narrowed it to a six-type tuple to
silence BLE001, which let gevent's LoopExit and similar kill the only
thread
driving the download queue - undoing #832's fix for #823 and resurfacing
it
  as #1166. GreenletExit and gevent.Timeout still propagate.

Fixes #1001
Refs #1166, #823
2026-08-11 02:25:28 -04:00
CaliBrain dfcd7c9b00 Fix silent Hardcover search failures on rejected sort values (#1183)
Hardcover forwards the `sort` argument to Typesense's `sort_by` and
rejects
the entire search if it dislikes the value -- an unknown field, a bare
field
name with no direction, or more than three sort keys. A rejected search
is
not a GraphQL error: it comes back as HTTP 200, no `errors` key, and a
null
`results` body.

_extract_typesense_hits() reads that null as `hits=[], found=0`, so a
failed
search was indistinguishable from one that matched nothing. Users saw
zero
results with a healthy container and no log line explaining why.

Add _execute_search_query(), used by the three sort-bearing call sites
(book
search, field typeahead, series resolution):

- Detect the rejection via the null `results` body. A search that
genuinely
matched nothing still returns a results object with `found: 0`, so empty
    result sets are not mistaken for failures.
- Retry once with an empty sort, which Hardcover always accepts, so
searches
    return results instead of nothing.
- Keep that fallback sticky for 15 minutes so every subsequent search
does
not pay for a request known to fail, and let it expire so sort order
comes
    back on its own if the index is fixed upstream.
- Log rejections that no sort can explain, and retries that also fail,
at
    ERROR instead of discarding them.

While the fallback is active, results fall back to Typesense's default
ordering regardless of the selected sort. Degraded ordering beats no
results,
and it is now logged rather than silent.

SORT_MAPPING itself is unchanged: all five of its values were verified
against
the live API and return results. The `sort: "relevance"` reported in
#1179 was
the raw SortOrder value sent by v1.3.5; the mapping already fixed that.
What
remained unfixed, and is fixed here, is that the failure was invisible.

Fixes #1179
2026-08-11 01:35:41 -04:00
dependabot[bot] bd9a22bd6c Bump node from a0b9bf0 to d32cdf6 in the docker-base-image-digests group (#1168)
Bumps the docker-base-image-digests group with 1 update: node.

Updates `node` from `a0b9bf0` to `d32cdf6`


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=node&package-manager=docker&previous-version=24-alpine&new-version=24-alpine)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 01:06:51 -04:00
dependabot[bot] ba4f7fb5e6 Bump the npm-deps group in /src/frontend with 8 updates (#1170)
Bumps the npm-deps group in /src/frontend with 8 updates:

| Package | From | To |
| --- | --- | --- |
|
[react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom)
| `7.18.1` | `7.18.2` |
|
[@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react)
| `19.2.17` | `19.2.18` |
|
[@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom)
| `19.2.3` | `19.2.4` |
|
[@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react)
| `6.0.4` | `6.0.5` |
| [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) |
`6.29.0` | `6.31.0` |
| [oxfmt](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt) |
`0.61.0` | `0.62.0` |
| [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) |
`1.76.0` | `1.77.0` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) |
`8.1.5` | `8.2.0` |

Updates `react-router-dom` from 7.18.1 to 7.18.2
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/remix-run/react-router/blob/react-router-dom@7.18.2/packages/react-router-dom/CHANGELOG.md">react-router-dom's
changelog</a>.</em></p>
<blockquote>
<h2>v7.18.2</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies:
<ul>
<li><a
href="https://github.com/remix-run/react-router/releases/tag/react-router@7.18.2"><code>react-router@7.18.2</code></a></li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/remix-run/react-router/commit/69a653ee6ab1ac95b13c917ec56c5f3dc17ca9c1"><code>69a653e</code></a>
Release v7.18.2 (<a
href="https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom/issues/15354">#15354</a>)</li>
<li>See full diff in <a
href="https://github.com/remix-run/react-router/commits/react-router-dom@7.18.2/packages/react-router-dom">compare
view</a></li>
</ul>
</details>
<br />

Updates `@types/react` from 19.2.17 to 19.2.18
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react">compare
view</a></li>
</ul>
</details>
<br />

Updates `@types/react-dom` from 19.2.3 to 19.2.4
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom">compare
view</a></li>
</ul>
</details>
<br />

Updates `@vitejs/plugin-react` from 6.0.4 to 6.0.5
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite-plugin-react/releases">@​vitejs/plugin-react's
releases</a>.</em></p>
<blockquote>
<h2>plugin-react@6.0.5</h2>
<h3>Fixed the react compiler preset filter to be linear (<a
href="https://redirect.github.com/vitejs/vite-plugin-react/pull/1353">#1353</a>)</h3>
<p>The improved filter in v6.0.3 was non-linear and caused a performance
regression (<a
href="https://redirect.github.com/vitejs/vite-plugin-react/issues/1349">#1349</a>).
The filter was changed to be linear to avoid that.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md">@​vitejs/plugin-react's
changelog</a>.</em></p>
<blockquote>
<h2>6.0.5 (2026-07-30)</h2>
<h3>Fixed the react compiler preset filter to be linear (<a
href="https://redirect.github.com/vitejs/vite-plugin-react/pull/1353">#1353</a>)</h3>
<p>The improved filter in v6.0.3 was non-linear and caused a performance
regression (<a
href="https://redirect.github.com/vitejs/vite-plugin-react/issues/1349">#1349</a>).
The filter was changed to be linear to avoid that.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/68c0cb8796ce18bd049c3d05c5210eaf0617eac0"><code>68c0cb8</code></a>
release: plugin-react@6.0.5 (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1362">#1362</a>)</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/555cdbc126506317b05404481374406771a41e70"><code>555cdbc</code></a>
fix(react): make the react compiler preset filter linear (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1353">#1353</a>)</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/a00a9f8240d5a7bb4062ee2a5bac68ea4f0defa6"><code>a00a9f8</code></a>
fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1327">#1327</a>)</li>
<li>See full diff in <a
href="https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.0.5/packages/plugin-react">compare
view</a></li>
</ul>
</details>
<br />

Updates `knip` from 6.29.0 to 6.31.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/webpro-nl/knip/releases">knip's
releases</a>.</em></p>
<blockquote>
<h2>Release 6.31.0</h2>
<ul>
<li>feat: Add basic support for Marko 6 and 5 (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1914">#1914</a>)
(c6b151642701f9b4864ddb5ea8e40580623f59d1) - thanks <a
href="https://github.com/caseycarroll"><code>@​caseycarroll</code></a>!</li>
<li>Use Array.from in Marko compiler
(5aadf47a22bdfd43bb8ad4082768e07fe37fc5ca)</li>
<li>Update release-it and fix release script flags
(6fa9c269c65c0923965924226a5ccf0778842967)</li>
<li>Downgrade <code>@​types/vscode</code> to match extension engines
range (6d8e9bf5200cf982d0bc801469c4e2be5627e5b8)</li>
</ul>
<h2>Release 6.30.0</h2>
<ul>
<li>docs: migrate to Starlight Blog plugin (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1854">#1854</a>)
(c89e3e3c9) - thanks <a
href="https://github.com/trueberryless"><code>@​trueberryless</code></a>!</li>
<li>Include workspace dependencies in scoped runs (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1901">#1901</a>)
(a75309eda)</li>
<li>Simplify workspace assembly (b3ac16411)</li>
<li>Document export declaration safety (ef98a7682)</li>
<li>Read object jsPlugin specifiers from Vite+ config (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1879">#1879</a>)
(3ea6a92d4) - thanks <a
href="https://github.com/matchai"><code>@​matchai</code></a>!</li>
<li>Compile gitignore negations once (6884cdb20)</li>
<li>Cache Node test script detection (d970f67d8)</li>
<li>Track only enabled workspace plugins (67d48268a)</li>
<li>Skip unsupported Vite config command (545bb2d36)</li>
<li>Add SARIF reporter (ce11cf5d4)</li>
<li>Track catalog refs in pnpm dlx scripts (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1885">#1885</a>,
close <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1886">#1886</a>)
(102189a1a)</li>
<li>Report unresolved catalog references (b2b86c3e5)</li>
<li>Track catalog references across scoped runs (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1905">#1905</a>)
(f0365bae0)</li>
<li>Track script catalog references across scoped runs (9b3ab3778)</li>
<li>Remove tsc from ignoreBinaries (a0a283040)</li>
<li>fix: detect dynamic imports in Svelte component templates (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1908">#1908</a>)
(aa303fb04) - thanks <a
href="https://github.com/firatciftci"><code>@​firatciftci</code></a>!</li>
<li>fix(remark): support all types of dependency names (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1911">#1911</a>)
(3441bea3a) - thanks <a
href="https://github.com/voxpelli"><code>@​voxpelli</code></a>!</li>
<li>Update sanity snapshot (d04aa04e5)</li>
<li>feat(stryker): add ts extensions (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1910">#1910</a>)
(cfc50886e) - thanks <a
href="https://github.com/christopher-buss"><code>@​christopher-buss</code></a>!</li>
<li>Adopt <code>@​e18e/eslint-plugin</code> (adf2511ab)</li>
<li>Remove Workleap from sponsors (d4f6af9ff)</li>
<li>Update dependencies (3f61542f6)</li>
<li>Add ES2023 array types to TypeScript 5 CI (360a47157)</li>
<li>Update sentry snapshot (7defc1853)</li>
<li>Analyze published type dependencies (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1909">#1909</a>)
(<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1912">#1912</a>)
(87d713e52)</li>
<li>Update TanStack and Code Forge logos (39ed8c82b)</li>
<li>Track assets referenced from SCSS URLs (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1916">#1916</a>)
(cbe13bf87)</li>
<li>Consume only the default export of inline dynamic import loaders
(resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1915">#1915</a>)
(b90a299e2)</li>
<li>Update ecosystem snapshots (3f6cdee15)</li>
<li>Update dependencies (6cdb3a6ac)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/webpro-nl/knip/commit/69c6006ee5938d7e2a6157222a24799eb639ec5c"><code>69c6006</code></a>
Release knip@6.31.0</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/5aadf47a22bdfd43bb8ad4082768e07fe37fc5ca"><code>5aadf47</code></a>
Use Array.from in Marko compiler</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/c6b151642701f9b4864ddb5ea8e40580623f59d1"><code>c6b1516</code></a>
feat: Add basic support for Marko 6 and 5 (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1914">#1914</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/837e52ca8601813b6bbbae13f6e7a614449de58a"><code>837e52c</code></a>
Release knip@6.30.0</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/6cdb3a6ac1f221d594fb4e71da00e80e6c2a90e6"><code>6cdb3a6</code></a>
Update dependencies</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/b90a299e2461f618f065cf3cfcededb1b4a4abc0"><code>b90a299</code></a>
Consume only the default export of inline dynamic import loaders
(resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1915">#1915</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/cbe13bf87d80f1d458453b913e5408c22413c11f"><code>cbe13bf</code></a>
Track assets referenced from SCSS URLs (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1916">#1916</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/87d713e5288820096bb675a283d873fb3af486e8"><code>87d713e</code></a>
Analyze published type dependencies (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1909">#1909</a>)
(<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1912">#1912</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/360a47157ec850064da0f946ff233d00e07f87c7"><code>360a471</code></a>
Add ES2023 array types to TypeScript 5 CI</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/3f61542f6eaf07b526d76e5f4f0293ae059ad32f"><code>3f61542</code></a>
Update dependencies</li>
<li>Additional commits viewable in <a
href="https://github.com/webpro-nl/knip/commits/knip@6.31.0/packages/knip">compare
view</a></li>
</ul>
</details>
<br />

Updates `oxfmt` from 0.61.0 to 0.62.0
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/blob/main/npm/oxfmt/CHANGELOG.md">oxfmt's
changelog</a>.</em></p>
<blockquote>
<h2>[0.62.0] - 2026-08-03</h2>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>e6e584b oxfmt: Type jsdoc. enum options (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/25008">#25008</a>)
(leaysgur)</li>
</ul>
<h2>[0.59.0] - 2026-07-13</h2>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>415fe1e oxfmt: Error on ignorePatterns that cannot match files
outside the config directory (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/24286">#24286</a>)
(leaysgur)</li>
</ul>
<h2>[0.55.0] - 2026-06-15</h2>
<h3>🚀 Features</h3>
<ul>
<li>9a2788b linter/unicorn: Implement <code>prefer-export-from</code>
rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/22935">#22935</a>)
(AliceLanniste)</li>
</ul>
<h2>[0.54.0] - 2026-06-08</h2>
<h3>📚 Documentation</h3>
<ul>
<li>dadafe3 oxlint, oxfmt: Mention migrate skills in npm READMEs (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/22965">#22965</a>)
(Boshen)</li>
<li>f88961a oxfmt: Annotate each config option with supported languages
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/22953">#22953</a>)
(leaysgur)</li>
</ul>
<h2>[0.52.0] - 2026-05-26</h2>
<h3>🚀 Features</h3>
<ul>
<li>16b8058 oxfmt: Support <code>vite-plus/resolveConfig</code> for
vite.config.ts (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/22454">#22454</a>)
(leaysgur)</li>
</ul>
<h2>[0.50.0] - 2026-05-15</h2>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>43b9978 formatter/sort_imports: Treat subpath imports as internal
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/22440">#22440</a>)
(leaysgur)</li>
</ul>
<h2>[0.49.0] - 2026-05-11</h2>
<h3>🚀 Features</h3>
<ul>
<li>6e8e818 oxfmt: Experimental .svelte support (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/21700">#21700</a>)
(leaysgur)</li>
</ul>
<h2>[0.45.0] - 2026-04-13</h2>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>50c389b oxfmt: Support <code>.editorconfig</code>
<code>quote_type</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/20989">#20989</a>)
(leaysgur)</li>
</ul>
<h2>[0.44.0] - 2026-04-06</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/commit/9a423f2f485b79c2353c49442c0c7f60f900261d"><code>9a423f2</code></a>
release(apps): oxlint v1.77.0 &amp;&amp; oxfmt v0.62.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/25251">#25251</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/e6e584b9bee0f85f45874854c79dbf23ae6e527a"><code>e6e584b</code></a>
fix(oxfmt): type jsdoc. enum options (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/25008">#25008</a>)</li>
<li>See full diff in <a
href="https://github.com/oxc-project/oxc/commits/oxfmt_v0.62.0/npm/oxfmt">compare
view</a></li>
</ul>
</details>
<br />

Updates `oxlint` from 1.76.0 to 1.77.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/releases">oxlint's
releases</a>.</em></p>
<blockquote>
<h2>oxlint v1.27.0 &amp;&amp; oxfmt v0.12.0</h2>
<h1>Oxlint v1.27.0</h1>
<h3>🚀 Features</h3>
<ul>
<li>222a8f0 linter/plugins: Implement
<code>SourceCode#isSpaceBetween</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15498">#15498</a>)
(overlookmotel)</li>
<li>2f9735d linter/plugins: Implement
<code>context.languageOptions</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15486">#15486</a>)
(overlookmotel)</li>
<li>bc731ff linter/plugins: Stub out all <code>Context</code> APIs (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15479">#15479</a>)
(overlookmotel)</li>
<li>5822cb4 linter/plugins: Add <code>extend</code> method to
<code>FILE_CONTEXT</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15477">#15477</a>)
(overlookmotel)</li>
<li>7b1e6f3 apps: Add pure rust binaries and release to github (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15469">#15469</a>)
(Boshen)</li>
<li>2a89b43 linter: Introduce debug assertions after fixes to assert
validity (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15389">#15389</a>)
(camc314)</li>
<li>ad3c45a editor: Add <code>oxc.path.node</code> option (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15040">#15040</a>)
(Sysix)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>6f3cd77 linter/no-var: Incorrect warning for blocks (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15504">#15504</a>)
(Hamir Mahal)</li>
<li>6957fb9 linter/plugins: Do not allow access to
<code>Context#id</code> in <code>createOnce</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15489">#15489</a>)
(overlookmotel)</li>
<li>7409630 linter/plugins: Allow access to <code>cwd</code> in
<code>createOnce</code> in ESLint interop mode (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15488">#15488</a>)
(overlookmotel)</li>
<li>732205e parser: Reject <code>using</code> / <code>await using</code>
in a switch <code>case</code> / <code>default</code> clause (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15225">#15225</a>)
(sapphi-red)</li>
<li>a17ca32 linter/plugins: Replace <code>Context</code> class (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15448">#15448</a>)
(overlookmotel)</li>
<li>ecf2f7b language_server: Fail gracefully when tsgolint executable
not found (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15436">#15436</a>)
(camc314)</li>
<li>3c8d3a7 lang-server: Improve logging in failure case for tsgolint
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15299">#15299</a>)
(camc314)</li>
<li>ef71410 linter: Use jsx if source type is JS in fix debug assertion
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15434">#15434</a>)
(camc314)</li>
<li>e32bbf6 linter/no-var: Handle TypeScript declare keyword in fixer
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15426">#15426</a>)
(camc314)</li>
<li>6565dbe linter/switch-case-braces: Skip comments when searching for
<code>:</code> token (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15425">#15425</a>)
(camc314)</li>
<li>85bd19a linter/prefer-class-fields: Insert value after type
annotation in fixer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15423">#15423</a>)
(camc314)</li>
<li>fde753e linter/plugins: Block access to
<code>context.settings</code> in <code>createOnce</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15394">#15394</a>)
(overlookmotel)</li>
<li>ddd9f9f linter/forward-ref-uses-ref: Dont suggest removing wrapper
in invalid positions (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15388">#15388</a>)
(camc314)</li>
<li>dac2a9c linter/no-template-curly-in-string: Remove fixer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15387">#15387</a>)
(camc314)</li>
<li>989b8e3 linter/no-var: Only fix to <code>const</code> if the var has
an initializer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15385">#15385</a>)
(camc314)</li>
<li>cc403f5 linter/plugins: Return empty object for unimplemented
parserServices (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15364">#15364</a>)
(magic-akari)</li>
</ul>
<h3>⚡ Performance</h3>
<ul>
<li>25d577e language_server: Start tools in parallel (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15500">#15500</a>)
(Sysix)</li>
<li>3c57291 linter/plugins: Optimize loops (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15449">#15449</a>)
(overlookmotel)</li>
<li>3166233 linter/plugins: Remove <code>Arc</code>s (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15431">#15431</a>)
(overlookmotel)</li>
<li>9de1322 linter/plugins: Lazily deserialize settings JSON (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15395">#15395</a>)
(overlookmotel)</li>
<li>3049ec2 linter/plugins: Optimize <code>deepFreezeSettings</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15392">#15392</a>)
(overlookmotel)</li>
<li>444ebfd linter/plugins: Use single object for
<code>parserServices</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15378">#15378</a>)
(overlookmotel)</li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>97d2104 linter: Update comment in lint.rs about default value for
tsconfig path (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15530">#15530</a>)
(Connor Shea)</li>
<li>2c6bd9e linter: Always refer as &quot;ES2015&quot; instead of
&quot;ES6&quot; (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15411">#15411</a>)
(sapphi-red)</li>
<li>a0c5203 linter/import/named: Update &quot;ES7&quot; comment in
examples (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15410">#15410</a>)
(sapphi-red)</li>
<li>3dc24b5 linter,minifier: Always refer as &quot;ES Modules&quot;
instead of &quot;ES6 Modules&quot; (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15409">#15409</a>)
(sapphi-red)</li>
<li>2ad77fb linter/no-this-before-super: Correct &quot;Why is this
bad?&quot; section (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15408">#15408</a>)
(sapphi-red)</li>
<li>57f0ce1 linter: Add backquotes where appropriate (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15407">#15407</a>)
(sapphi-red)</li>
</ul>
<h1>Oxfmt v0.12.0</h1>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md">oxlint's
changelog</a>.</em></p>
<blockquote>
<h2>[1.77.0] - 2026-08-03</h2>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>5c0fa61 linter/eslint/no-warning-comments: Unify config structs and
remove manual options docs (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25151">#25151</a>)
(Mikhail Baev)</li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>9dc7756 linter/typescript/no-unnecessary-condition: Clarify options
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25110">#25110</a>)
(camc314)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/commit/9a423f2f485b79c2353c49442c0c7f60f900261d"><code>9a423f2</code></a>
release(apps): oxlint v1.77.0 &amp;&amp; oxfmt v0.62.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25251">#25251</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/5c0fa61ddbd098fea5101dff356dc9b67adbee95"><code>5c0fa61</code></a>
fix(linter/eslint/no-warning-comments): unify config structs and remove
manua...</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/9dc77567291db6ebd0641284f80bf50163c6a257"><code>9dc7756</code></a>
docs(linter/typescript/no-unnecessary-condition): clarify options (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/25110">#25110</a>)</li>
<li>See full diff in <a
href="https://github.com/oxc-project/oxc/commits/oxlint_v1.77.0/npm/oxlint">compare
view</a></li>
</ul>
</details>
<br />

Updates `vite` from 8.1.5 to 8.2.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/releases">vite's
releases</a>.</em></p>
<blockquote>
<h2>create-vite@8.2.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/create-vite@8.2.0/packages/create-vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>plugin-legacy@8.2.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/plugin-legacy@8.2.0/packages/plugin-legacy/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v8.2.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v8.2.0/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v8.2.0-beta.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v8.2.0-beta.0/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md">vite's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/vitejs/vite/compare/v8.2.0-beta.0...v8.2.0">8.2.0</a>
(2026-07-30)</h2>
<h3>Features</h3>
<ul>
<li>add <code>input</code> to <code>server.fs.allow</code> (<a
href="https://redirect.github.com/vitejs/vite/issues/23035">#23035</a>)
(<a
href="https://github.com/vitejs/vite/commit/95a3cdab83e1125b03d2e8dd942fb6b64209e5fa">95a3cda</a>)</li>
<li><strong>bundled-dev:</strong> reload once after rebuild instead of
via the fallback page (<a
href="https://redirect.github.com/vitejs/vite/issues/23106">#23106</a>)
(<a
href="https://github.com/vitejs/vite/commit/b24381d741941b9ce2b1c07db62cc5f4d7bad981">b24381d</a>)</li>
<li><strong>bundled-dev:</strong> support worker file update accepted by
HMR (<a
href="https://redirect.github.com/vitejs/vite/issues/23068">#23068</a>)
(<a
href="https://github.com/vitejs/vite/commit/0d04351fdc12258c75b9f1cda5780fdb836ed0ef">0d04351</a>)</li>
<li><strong>config:</strong> include column in config incompatibility
location (<a
href="https://redirect.github.com/vitejs/vite/issues/23064">#23064</a>)
(<a
href="https://github.com/vitejs/vite/commit/8a245726944ed29225920d49be77c33c6e03afc8">8a24572</a>)</li>
<li><strong>dev:</strong> resolve interface name for explicit host in
network URLs (<a
href="https://redirect.github.com/vitejs/vite/issues/22965">#22965</a>)
(<a
href="https://github.com/vitejs/vite/commit/3ac77d9dd742968961af38a5a91ed6b061ceda7d">3ac77d9</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>bundledDev:</strong> print build errors to the terminal when
an HMR update fails (<a
href="https://redirect.github.com/vitejs/vite/issues/23024">#23024</a>)
(<a
href="https://github.com/vitejs/vite/commit/41c465896e8b11b1eb9c5fbdafbdcc528e189a2c">41c4658</a>)</li>
<li><strong>deps:</strong> update all non-major dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/23069">#23069</a>)
(<a
href="https://github.com/vitejs/vite/commit/4c07b74416f859d7e8bdace13409ef2d080edf76">4c07b74</a>)</li>
<li><strong>hmr:</strong> preserve environment snapshot during server
restart (<a
href="https://redirect.github.com/vitejs/vite/issues/22992">#22992</a>)
(<a
href="https://github.com/vitejs/vite/commit/b1186c36d06bb94941c58e8272fc4acb8512c93b">b1186c3</a>)</li>
<li><strong>importAnalysis:</strong> interop imports injected into
optimized dep files by plugins (<a
href="https://redirect.github.com/vitejs/vite/issues/23029">#23029</a>)
(<a
href="https://github.com/vitejs/vite/commit/8c2a87d41fb24536e59643351758084cde4d0dd7">8c2a87d</a>)</li>
<li><strong>module-runner:</strong> keep stack trace interception
working when <code>Object.prototype</code> is frozen (<a
href="https://redirect.github.com/vitejs/vite/issues/23073">#23073</a>)
(<a
href="https://github.com/vitejs/vite/commit/599c5b02a8b6879b05ede988020f1331e877aaea">599c5b0</a>)</li>
<li><strong>server:</strong> strip base in indexHtml module graph lookup
(<a
href="https://redirect.github.com/vitejs/vite/issues/22932">#22932</a>)
(<a
href="https://github.com/vitejs/vite/commit/fa005d19af5d847931c6dbefc63841c137383e6c">fa005d1</a>)</li>
<li>support resolving top-level input option with plugins (<a
href="https://redirect.github.com/vitejs/vite/issues/23101">#23101</a>)
(<a
href="https://github.com/vitejs/vite/commit/41df81a6a4c3eef08f7a9a8ac9530cd136c0eafa">41df81a</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li><strong>config:</strong> correct cacheDir default fallback
description (<a
href="https://redirect.github.com/vitejs/vite/issues/23060">#23060</a>)
(<a
href="https://github.com/vitejs/vite/commit/aafa103af5d71fb59d7c3dd617d0cbef3b222f1f">aafa103</a>)</li>
</ul>
<h3>Tests</h3>
<ul>
<li>config CJS module vars in ESM case (<a
href="https://redirect.github.com/vitejs/vite/issues/23010">#23010</a>)
(<a
href="https://github.com/vitejs/vite/commit/d8cd38830251b95fd7dddcd0eee0ce94cc61c2f4">d8cd388</a>)</li>
</ul>
<h2><a
href="https://github.com/vitejs/vite/compare/v8.1.5...v8.2.0-beta.0">8.2.0-beta.0</a>
(2026-07-22)</h2>
<h3>Features</h3>
<ul>
<li>add <code>input</code> option (<a
href="https://redirect.github.com/vitejs/vite/issues/22642">#22642</a>)
(<a
href="https://github.com/vitejs/vite/commit/9beae37d7221b25463a011feb40b0303ca328d87">9beae37</a>)</li>
<li><strong>config:</strong> warn features incompatible with native
loader in bundle loader (<a
href="https://redirect.github.com/vitejs/vite/issues/22850">#22850</a>)
(<a
href="https://github.com/vitejs/vite/commit/05302b07267f6b4f9dbeac5b1d73fcc3dc06d730">05302b0</a>)</li>
<li><strong>css:</strong> export PostCSS config type for type-safe
configs (<a
href="https://redirect.github.com/vitejs/vite/issues/22792">#22792</a>)
(<a
href="https://github.com/vitejs/vite/commit/302c755a8125b9a26214e3b413922b5513e41981">302c755</a>)</li>
<li><strong>dev:</strong> label network URLs with their interface name
(<a
href="https://redirect.github.com/vitejs/vite/issues/22830">#22830</a>)
(<a
href="https://github.com/vitejs/vite/commit/78accc42a5b8887d9df624f7d4a934d3ead677d1">78accc4</a>)</li>
<li><strong>optimizer:</strong> support aube lockfile (<a
href="https://redirect.github.com/vitejs/vite/issues/22813">#22813</a>)
(<a
href="https://github.com/vitejs/vite/commit/6319827116c5be2a19c1b91c84ba3d38ad26a41c">6319827</a>)</li>
<li><strong>optimizer:</strong> support nub lockfile (<a
href="https://redirect.github.com/vitejs/vite/issues/22891">#22891</a>)
(<a
href="https://github.com/vitejs/vite/commit/65d3604f6fdbfcf6e86244d7fe3c1ca86acae701">65d3604</a>)</li>
<li>update rolldown-related dependencies and use client-side HMR in
bundled-dev (<a
href="https://redirect.github.com/vitejs/vite/issues/22961">#22961</a>)
(<a
href="https://github.com/vitejs/vite/commit/960e9efbc1372000caac46cc2f123cef4824e2bb">960e9ef</a>)</li>
<li><strong>wasm:</strong> expand test suite, unwrap WebAssembly.Global
and enable js-string builtins (<a
href="https://redirect.github.com/vitejs/vite/issues/22674">#22674</a>)
(<a
href="https://github.com/vitejs/vite/commit/9e79b51579457a9af4fa623b68a0bfabbf38010b">9e79b51</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>build:</strong> map CSS chunks in chunk import maps (fix <a
href="https://redirect.github.com/vitejs/vite/issues/22946">#22946</a>)
(<a
href="https://redirect.github.com/vitejs/vite/issues/22947">#22947</a>)
(<a
href="https://github.com/vitejs/vite/commit/e16ff3a1199293ac9cdfa6132c08fdea162215f3">e16ff3a</a>)</li>
<li><strong>config:</strong> exclude virtual modules from native config
compat check (<a
href="https://redirect.github.com/vitejs/vite/issues/22979">#22979</a>)
(<a
href="https://github.com/vitejs/vite/commit/2ced1fe4e4e480ed78cb7aa5c78319e57bfa7783">2ced1fe</a>)</li>
<li><strong>css:</strong> rewrite urls in OnceExit-injected content (<a
href="https://redirect.github.com/vitejs/vite/issues/22983">#22983</a>)
(<a
href="https://github.com/vitejs/vite/commit/abb793e18c92592c21fbb8e1f3fc450b5839f04f">abb793e</a>)</li>
<li><strong>deps:</strong> update all non-major dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/22985">#22985</a>)
(<a
href="https://github.com/vitejs/vite/commit/04f345b37064cd0bba6447eb5c32be5c22162f3d">04f345b</a>)</li>
<li><strong>deps:</strong> update dependency magic-string to v1 (<a
href="https://redirect.github.com/vitejs/vite/issues/22998">#22998</a>)
(<a
href="https://github.com/vitejs/vite/commit/c60b4d7cdb85b7d4f78671cdcfb863e5f8b66bb7">c60b4d7</a>)</li>
<li><strong>hmr:</strong> remove hot data after prune (<a
href="https://redirect.github.com/vitejs/vite/issues/23002">#23002</a>)
(<a
href="https://github.com/vitejs/vite/commit/be9631658f5191ee5c5665e780239d42a330280a">be96316</a>)</li>
<li>resolve root to real path (<a
href="https://redirect.github.com/vitejs/vite/issues/22832">#22832</a>)
(<a
href="https://github.com/vitejs/vite/commit/55bba7bbd9de40d031360e4408fe91bff5b29ec9">55bba7b</a>)</li>
</ul>
<h3>Performance Improvements</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/vitejs/vite/commit/24a611f1c83a976d32262628d42f683609746635"><code>24a611f</code></a>
release: v7.2.4</li>
<li><a
href="https://github.com/vitejs/vite/commit/2d66b7b14aa6dfd62f3d6a59ee8382ed5ca6fd32"><code>2d66b7b</code></a>
fix: revert &quot;perf(deps): replace debug with obug (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21107">#21107</a>)&quot;</li>
<li><a
href="https://github.com/vitejs/vite/commit/a668014dba377c2b82a32d8124f1761e9ea74f82"><code>a668014</code></a>
release: v7.2.3</li>
<li><a
href="https://github.com/vitejs/vite/commit/acfe939e1f7c303c34b0b39b883cc302da767fa2"><code>acfe939</code></a>
perf(deps): replace debug with obug (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21107">#21107</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/4f8171eb3046bd70c83964689897dab4c6b58bc0"><code>4f8171e</code></a>
fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21128">#21128</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/50297208452241061cb44d09a4bbdf77a11ac01e"><code>5029720</code></a>
chore(deps): update rolldown-related dependencies (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21127">#21127</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/5909efd8fbfd1bf1eab65427aea0613124b2797a"><code>5909efd</code></a>
fix: allow multiple <code>bindCLIShortcuts</code> calls with shortcut
merging (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21103">#21103</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/39a0a15fd24ed37257c48b795097a3794e54d255"><code>39a0a15</code></a>
chore(deps): update rolldown-related dependencies (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21095">#21095</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/6a34ac3422686e7cf7cc9a25d299cb8e5a8d92a0"><code>6a34ac3</code></a>
fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21096">#21096</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/02ceaec45e17bef19159188a28d9196fed1761be"><code>02ceaec</code></a>
chore(deps): update dependency <code>@​rollup/plugin-commonjs</code> to
v29 (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21099">#21099</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/vitejs/vite/commits/create-vite@8.2.0/packages/vite">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 01:06:37 -04:00
dependabot[bot] f1de248ed5 Bump the gh-actions group with 4 updates (#1171)
Bumps the gh-actions group with 4 updates:
[docker/login-action](https://github.com/docker/login-action),
[github/codeql-action/init](https://github.com/github/codeql-action),
[github/codeql-action/autobuild](https://github.com/github/codeql-action)
and
[github/codeql-action/analyze](https://github.com/github/codeql-action).

Updates `docker/login-action` from 4.5.2 to 4.6.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/docker/login-action/releases">docker/login-action's
releases</a>.</em></p>
<blockquote>
<h2>v4.6.0</h2>
<ul>
<li>Harden buildx scoped config path handling by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/login-action/pull/1059">docker/login-action#1059</a></li>
<li>Bump <code>@​aws-sdk/client-ecr</code> and
<code>@​aws-sdk/client-ecr-public</code> to 3.1095.0 in <a
href="https://redirect.github.com/docker/login-action/pull/1051">docker/login-action#1051</a></li>
<li>Bump js-yaml from 5.2.1 to 5.2.2 in <a
href="https://redirect.github.com/docker/login-action/pull/1057">docker/login-action#1057</a></li>
<li>Bump postcss from 8.5.10 to 8.5.22 in <a
href="https://redirect.github.com/docker/login-action/pull/1056">docker/login-action#1056</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/login-action/compare/v4.5.2...v4.6.0">https://github.com/docker/login-action/compare/v4.5.2...v4.6.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/docker/login-action/commit/dbcb813823bdd20940b903addbd779551569679f"><code>dbcb813</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/1051">#1051</a>
from docker/dependabot/npm_and_yarn/aws-sdk-dependen...</li>
<li><a
href="https://github.com/docker/login-action/commit/5bcb015ee6ec720ecdeaef2dc1164122e9b209fc"><code>5bcb015</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/login-action/commit/b30b2f2d3196c1714318ba0c3c3bec211d949752"><code>b30b2f2</code></a>
build(deps): bump the aws-sdk-dependencies group across 1 directory with
2 up...</li>
<li><a
href="https://github.com/docker/login-action/commit/9087f1e6d666fe0292409e3c819680c18526e108"><code>9087f1e</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/1057">#1057</a>
from docker/dependabot/npm_and_yarn/js-yaml-5.2.2</li>
<li><a
href="https://github.com/docker/login-action/commit/0009830ea169ca16c24c0ea4cac1c325bfa3aee4"><code>0009830</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/login-action/commit/23255232d3e43c8f0052d9a0dba82a515a88ce92"><code>2325523</code></a>
build(deps): bump js-yaml from 5.2.1 to 5.2.2</li>
<li><a
href="https://github.com/docker/login-action/commit/4ec1d4a769e8b05a89a7396551dc38b329211688"><code>4ec1d4a</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/1056">#1056</a>
from docker/dependabot/npm_and_yarn/postcss-8.5.22</li>
<li><a
href="https://github.com/docker/login-action/commit/5fc99ba47bca274c5a499688f71c7ea79c0ea1b3"><code>5fc99ba</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/1053">#1053</a>
from docker/dependabot/github_actions/aws-actions/co...</li>
<li><a
href="https://github.com/docker/login-action/commit/e512bd59d16c53d79ea5c0f0e345fe554453c4bb"><code>e512bd5</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/1052">#1052</a>
from docker/dependabot/github_actions/codeql-actions...</li>
<li><a
href="https://github.com/docker/login-action/commit/a146c91b8f371700d323bae808af7cbdc2766ed5"><code>a146c91</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/1059">#1059</a>
from crazy-max/harden-buildx-scope-paths</li>
<li>Additional commits viewable in <a
href="https://github.com/docker/login-action/compare/371161bbe7024a29a25c5e19bfcbc0804fe9ad2c...dbcb813823bdd20940b903addbd779551569679f">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/init` from 4.37.3 to 4.37.6
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/init's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.6</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>v4.37.5</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>v4.37.4</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/init's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/5595ccaf912efad79be6eef63a5619ff05969be3"><code>5595cca</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4071">#4071</a>
from github/update-v4.37.6-6a9359a1b</li>
<li><a
href="https://github.com/github/codeql-action/commit/ec9c75796a7f2cee5af0c5ffa0b81dc3bb58754b"><code>ec9c757</code></a>
Add change note for PR 4070</li>
<li><a
href="https://github.com/github/codeql-action/commit/45c8742e17cbd668814137f95e605d925b8722a2"><code>45c8742</code></a>
Update changelog for v4.37.6</li>
<li><a
href="https://github.com/github/codeql-action/commit/6a9359a1bd054c53cae7bb737bd8d796cfbf3014"><code>6a9359a</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4070">#4070</a>
from github/mbg/remote-address/change-file-default</li>
<li><a
href="https://github.com/github/codeql-action/commit/065cdc0394d424981db720df63ebc570e41b775f"><code>065cdc0</code></a>
Change <code>DEFAULT_CONFIG_FILE_NAME</code></li>
<li><a
href="https://github.com/github/codeql-action/commit/f99dd5aeee9cf92e92d0c700cb0aa7afd7bbf431"><code>f99dd5a</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4066">#4066</a>
from github/dependabot/npm_and_yarn/js-yaml-5.2.2</li>
<li><a
href="https://github.com/github/codeql-action/commit/1804b211a343d69a6584d26fb3a68a8fe6ca39d4"><code>1804b21</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4068">#4068</a>
from github/mergeback/v4.37.5-to-main-d1ba80a1</li>
<li><a
href="https://github.com/github/codeql-action/commit/3020a2f46286abb1704269b22ada83bd0e81c64f"><code>3020a2f</code></a>
Rebuild</li>
<li><a
href="https://github.com/github/codeql-action/commit/93c3a5a40b7affbf8ea6a480767ed0db8e8d3c5c"><code>93c3a5a</code></a>
Update changelog and version after v4.37.5</li>
<li><a
href="https://github.com/github/codeql-action/commit/d1ba80a13dd99fba24a470575428917156a28b43"><code>d1ba80a</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4067">#4067</a>
from github/update-v4.37.5-1cd4d01d5</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81...5595ccaf912efad79be6eef63a5619ff05969be3">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/autobuild` from 4.37.3 to 4.37.6
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/autobuild's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.6</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>v4.37.5</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>v4.37.4</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/autobuild's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/5595ccaf912efad79be6eef63a5619ff05969be3"><code>5595cca</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4071">#4071</a>
from github/update-v4.37.6-6a9359a1b</li>
<li><a
href="https://github.com/github/codeql-action/commit/ec9c75796a7f2cee5af0c5ffa0b81dc3bb58754b"><code>ec9c757</code></a>
Add change note for PR 4070</li>
<li><a
href="https://github.com/github/codeql-action/commit/45c8742e17cbd668814137f95e605d925b8722a2"><code>45c8742</code></a>
Update changelog for v4.37.6</li>
<li><a
href="https://github.com/github/codeql-action/commit/6a9359a1bd054c53cae7bb737bd8d796cfbf3014"><code>6a9359a</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4070">#4070</a>
from github/mbg/remote-address/change-file-default</li>
<li><a
href="https://github.com/github/codeql-action/commit/065cdc0394d424981db720df63ebc570e41b775f"><code>065cdc0</code></a>
Change <code>DEFAULT_CONFIG_FILE_NAME</code></li>
<li><a
href="https://github.com/github/codeql-action/commit/f99dd5aeee9cf92e92d0c700cb0aa7afd7bbf431"><code>f99dd5a</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4066">#4066</a>
from github/dependabot/npm_and_yarn/js-yaml-5.2.2</li>
<li><a
href="https://github.com/github/codeql-action/commit/1804b211a343d69a6584d26fb3a68a8fe6ca39d4"><code>1804b21</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4068">#4068</a>
from github/mergeback/v4.37.5-to-main-d1ba80a1</li>
<li><a
href="https://github.com/github/codeql-action/commit/3020a2f46286abb1704269b22ada83bd0e81c64f"><code>3020a2f</code></a>
Rebuild</li>
<li><a
href="https://github.com/github/codeql-action/commit/93c3a5a40b7affbf8ea6a480767ed0db8e8d3c5c"><code>93c3a5a</code></a>
Update changelog and version after v4.37.5</li>
<li><a
href="https://github.com/github/codeql-action/commit/d1ba80a13dd99fba24a470575428917156a28b43"><code>d1ba80a</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4067">#4067</a>
from github/update-v4.37.5-1cd4d01d5</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81...5595ccaf912efad79be6eef63a5619ff05969be3">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/analyze` from 4.37.3 to 4.37.6
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/analyze's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.6</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>v4.37.5</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>v4.37.4</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/analyze's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/5595ccaf912efad79be6eef63a5619ff05969be3"><code>5595cca</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4071">#4071</a>
from github/update-v4.37.6-6a9359a1b</li>
<li><a
href="https://github.com/github/codeql-action/commit/ec9c75796a7f2cee5af0c5ffa0b81dc3bb58754b"><code>ec9c757</code></a>
Add change note for PR 4070</li>
<li><a
href="https://github.com/github/codeql-action/commit/45c8742e17cbd668814137f95e605d925b8722a2"><code>45c8742</code></a>
Update changelog for v4.37.6</li>
<li><a
href="https://github.com/github/codeql-action/commit/6a9359a1bd054c53cae7bb737bd8d796cfbf3014"><code>6a9359a</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4070">#4070</a>
from github/mbg/remote-address/change-file-default</li>
<li><a
href="https://github.com/github/codeql-action/commit/065cdc0394d424981db720df63ebc570e41b775f"><code>065cdc0</code></a>
Change <code>DEFAULT_CONFIG_FILE_NAME</code></li>
<li><a
href="https://github.com/github/codeql-action/commit/f99dd5aeee9cf92e92d0c700cb0aa7afd7bbf431"><code>f99dd5a</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4066">#4066</a>
from github/dependabot/npm_and_yarn/js-yaml-5.2.2</li>
<li><a
href="https://github.com/github/codeql-action/commit/1804b211a343d69a6584d26fb3a68a8fe6ca39d4"><code>1804b21</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4068">#4068</a>
from github/mergeback/v4.37.5-to-main-d1ba80a1</li>
<li><a
href="https://github.com/github/codeql-action/commit/3020a2f46286abb1704269b22ada83bd0e81c64f"><code>3020a2f</code></a>
Rebuild</li>
<li><a
href="https://github.com/github/codeql-action/commit/93c3a5a40b7affbf8ea6a480767ed0db8e8d3c5c"><code>93c3a5a</code></a>
Update changelog and version after v4.37.5</li>
<li><a
href="https://github.com/github/codeql-action/commit/d1ba80a13dd99fba24a470575428917156a28b43"><code>d1ba80a</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4067">#4067</a>
from github/update-v4.37.5-1cd4d01d5</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81...5595ccaf912efad79be6eef63a5619ff05969be3">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 01:06:28 -04:00
dependabot[bot] 78d5d0632a Bump the python-deps group across 1 directory with 5 updates (#1182)
Bumps the python-deps group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [python-socketio](https://github.com/miguelgrinberg/python-socketio) |
`5.16.3` | `5.16.4` |
| [rarfile](https://github.com/markokr/rarfile) | `4.4` | `4.5` |
| [seleniumbase](https://github.com/seleniumbase/SeleniumBase) |
`4.51.8` | `4.51.11` |
| [prek](https://github.com/j178/prek) | `0.4.11` | `0.4.12` |
| [ruff](https://github.com/astral-sh/ruff) | `0.16.1` | `0.16.2` |


Updates `python-socketio` from 5.16.3 to 5.16.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/miguelgrinberg/python-socketio/releases">python-socketio's
releases</a>.</em></p>
<blockquote>
<h2>Release 5.16.4</h2>
<p>See <a
href="https://github.com/miguelgrinberg/python-socketio/blob/main/CHANGES.md">CHANGES.md</a>
for release notes.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/miguelgrinberg/python-socketio/blob/main/CHANGES.md">python-socketio's
changelog</a>.</em></p>
<blockquote>
<h1>python-socketio change log</h1>
<p><strong>Release 5.16.4</strong> - 2026-08-07</p>
<ul>
<li>Delete incomplete binary packet when client disconnects (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/4bec3ef87bcfd6ab5b94cd3ac09d873283a6960e">commit</a>)</li>
</ul>
<p><strong>Release 5.16.3</strong> - 2026-06-15</p>
<ul>
<li>Catch all exceptions in redis and rabbitmq client managers <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1581">#1581</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/80bb5c9b07e6b53e45f610ec29a24124c539d41b">commit</a>)</li>
</ul>
<p><strong>Release 5.16.2</strong> - 2026-05-21</p>
<ul>
<li>Prevent unnecessary resource allocations <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1574">#1574</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/ca140fe44d0ceb3004073645222abec182d8784b">commit</a>)</li>
<li>Add zizmor to CI builds <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1570">#1570</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/664dc27ec6f34179ed1724430eda3520627fc642">commit</a>)</li>
</ul>
<p><strong>Release 5.16.1</strong> - 2026-02-06</p>
<ul>
<li>Use configured JSON module in managers <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1549">#1549</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/6229261ae6e8c01e675097242e333ee84587a544">commit</a>)</li>
<li>Admin UI fixes: remove duplicate tasks, report transport upgrades
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/1c2eab13a92fac9e43663eb0b5f099eb1c40ea5b">commit</a>)</li>
<li>Switch to Furo documentation template (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/add47d8c7abca697a2804141bbf29bfb095f7d5e">commit</a>)</li>
<li>Add Python free-threading to CI <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1554">#1554</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/ccdd2004a038ae4b8171a05120c5d0787332f7ee">commit</a>)</li>
</ul>
<p><strong>Release 5.16.0</strong> - 2025-12-24</p>
<ul>
<li>Address deprecation warnings (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/b235699d9b06564753c570b76055997e9d62a938">commit</a>)</li>
<li>Drop Python 3.8 and 3.9 from CI builds (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/d0728d2f74538762dd551fa9cd0cd1fd5aedfa37">commit</a>)</li>
</ul>
<p><strong>Release 5.15.1</strong> - 2025-12-16</p>
<ul>
<li>Restore support multiple arguments via pubsub emits <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1540">#1540</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/c279f26bb8c9887c4ca99d4d81ad331c4844438c">commit</a>)</li>
</ul>
<p><strong>Release 5.15.0</strong> - 2025-11-22</p>
<ul>
<li>Retry initial Redis connection <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1536">#1534</a>
([commit <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1">#1</a>](<a
href="https://github.com/miguelgrinberg/python-socketio/commit/1e903e173a2d7b04599c4f7f9630c1abbb531fad">https://github.com/miguelgrinberg/python-socketio/commit/1e903e173a2d7b04599c4f7f9630c1abbb531fad</a>)
[commit <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/2">#2</a>](<a
href="https://github.com/miguelgrinberg/python-socketio/commit/5e898a9b93526e6e667767e54c60f4c84589989d">https://github.com/miguelgrinberg/python-socketio/commit/5e898a9b93526e6e667767e54c60f4c84589989d</a>))</li>
<li>Correctly regenerate RabbitMQ binding after a connection failure <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1516">#1516</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/c52e93b4a328d98a968bfbdec0cfd598b73ee913">commit</a>)
(thanks <strong>Gritty_dev</strong>!)</li>
<li>Support <code>ext_type</code> in the <code>MsgPackPacket</code>
class <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1521">#1521</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/208925344a48485d2cd56e40eb74266c3bcb5311">commit</a>)</li>
<li>Support sending <code>bytesarray</code>s when using pub/sub managers
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/6c9b9974f72e2efdf62407ecab24ee6995448098">commit</a>)</li>
<li>Fix typos in documentation <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1520">#1520</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/db3f1c2a0105c30cb833ddfca8f05fe4320468fd">commit</a>)
(thanks <strong>Lê Nam Khánh</strong>!)</li>
<li>Improvements to the logging documentation (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/b423d0e38eef559b7e81acb7e32059de305f982c">commit</a>)</li>
</ul>
<p><strong>Release 5.14.3</strong> - 2025-10-29</p>
<ul>
<li>Support Python's native <code>ConnectionRefusedError</code>
exception to reject a connection <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1515">#1515</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/f3b18bde3f16437b223491d4c3e440ea37105fe3">commit</a>)</li>
<li>Push binary data to the aiopika client manager <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1514">#1514</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/194e1b7f277b5f72e1de78d3f614e7b8b6c788ac">commit</a>)</li>
</ul>
<p><strong>Release 5.14.2</strong> - 2025-10-15</p>
<ul>
<li>Restore binary message support in message queue setups <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1509">#1509</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/bab4a10f48aaae11d7f832ebe5c30ad3f85d31b3">commit</a>)</li>
<li>Fix formatting of client connection error <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1507">#1507</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/f298c9b54d76ab09ff72935937e1b9575bc45ffd">commit</a>)</li>
<li>Add 3.14 and pypy-3.11 CI tasks (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/1f4cd3b025c294f25208ec3c05b5f8df6209e403">commit</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/d0cf60952281c0b8f41a7a73f01da8129da89032"><code>d0cf609</code></a>
Release 5.16.4</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/4bec3ef87bcfd6ab5b94cd3ac09d873283a6960e"><code>4bec3ef</code></a>
Delete incomplete binary packet when client disconnects</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/1e44ea3be1258ca2344872a98a4840743738080e"><code>1e44ea3</code></a>
Bump socket.io-parser from 4.2.6 to 4.2.7 in /examples/client/javascript
(<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/15">#15</a>...</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/a8568a449f28a81decb2f3d7adc7b0a2af88e12c"><code>a8568a4</code></a>
Bump aiohttp from 3.14.1 to 3.14.3 in /examples/server/aiohttp (<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1591">#1591</a>)
#nolog</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/3826ad453ba50b7d6876180a149a5b2fb0a01e73"><code>3826ad4</code></a>
Bump body-parser from 1.20.5 to 1.20.6 in /examples/client/javascript
(<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1590">#1590</a>)...</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/2d21903008b185b66ee644bfe231bb953298e9b1"><code>2d21903</code></a>
Remove failing codecov service</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/cb558519ffcc290974478902ceebd7d1a96a220e"><code>cb55851</code></a>
Bump aiohttp from 3.14.0 to 3.14.1 in /examples/server/aiohttp (<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1584">#1584</a>)
#nolog</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/71585b1df83f36029b1ead9c1b4cb42da85192de"><code>71585b1</code></a>
Bump tornado from 6.5.5 to 6.5.7 in /examples/server/tornado (<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1583">#1583</a>)
#nolog</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/02950be3756c3baedd63b5109de9ddac0af10a99"><code>02950be</code></a>
Version 5.16.4.dev0</li>
<li>See full diff in <a
href="https://github.com/miguelgrinberg/python-socketio/compare/v5.16.3...v5.16.4">compare
view</a></li>
</ul>
</details>
<br />

Updates `rarfile` from 4.4 to 4.5
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/markokr/rarfile/releases">rarfile's
releases</a>.</em></p>
<blockquote>
<h2>rarfile v4.5</h2>
<p>Security fixes:</p>
<ul>
<li>Skip comments that are larger than 256k.</li>
</ul>
<p>Fixes:</p>
<ul>
<li>Truncate filenames at NUL byte.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/markokr/rarfile/blob/master/doc/news.rst">rarfile's
changelog</a>.</em></p>
<blockquote>
<h2>Version 4.5 (2026-08-02)</h2>
<p>Security fixes:</p>
<ul>
<li>Skip comments that are larger than 256k.
[<code>GHSA-94vx-95fq-wwvp
&lt;https://github.com/markokr/rarfile/security/advisories/GHSA-94vx-95fq-wwvp&gt;</code>_]</li>
</ul>
<p>Fixes:</p>
<ul>
<li>Truncate filenames at NUL byte.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/markokr/rarfile/commit/60c8513276ea462c39822db487c10111b9039442"><code>60c8513</code></a>
Refresh apt index</li>
<li><a
href="https://github.com/markokr/rarfile/commit/d62647091dc24bb384788c75b966d9dd4f0deaaf"><code>d626470</code></a>
v4.4</li>
<li><a
href="https://github.com/markokr/rarfile/commit/8321daa5417a62adce0db160510a1cdc43744af0"><code>8321daa</code></a>
Limit comment size</li>
<li><a
href="https://github.com/markokr/rarfile/commit/be6a4a69069852dcc16af3f6ad180e0266cbf15a"><code>be6a4a6</code></a>
Truncate filenames at NUL</li>
<li>See full diff in <a
href="https://github.com/markokr/rarfile/compare/v4.4...v4.5">compare
view</a></li>
</ul>
</details>
<br />

Updates `seleniumbase` from 4.51.8 to 4.51.11
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.51.11 - CDP Mode: Patch 127</h2>
<h2>CDP Mode: Patch 127</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/3ba187938eca01c8582c0c832b5d00c44105b96f">Fix
memory leaks, fix bugs, and update timing</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/8372dbc85d60c504c678a738df648791d712aec1">Refresh
Python dependencies</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/710ae0abd46732bd145e578b6a22fb5eecde27b9">Update
examples</a></li>
</ul>
<p>Special thanks to <a
href="https://github.com/crippledgeek"><code>@​crippledgeek</code></a>
(<a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4454">seleniumbase/SeleniumBase#4454</a>
and <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4455">seleniumbase/SeleniumBase#4455</a>)</p>
<h2>What's Changed</h2>
<ul>
<li>CDP Mode: Patch 127 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4457">seleniumbase/SeleniumBase#4457</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.51.10...v4.51.11">https://github.com/seleniumbase/SeleniumBase/compare/v4.51.10...v4.51.11</a></p>
<h2>4.51.10 - CDP Mode: Patch 126</h2>
<h2>CDP Mode: Patch 126</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/a7e28ac407fc63afdcfbaadc642b5a97b77a1375">Patch
the vendored version of nest_asyncio for Python 3.14+</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/076956c14a944b25ff191189b32123220e490fff">Update
timing of stealthy CDP Mode clicks</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/0455ee62040c1b5185430035fd851518a9c83b84">Refresh
Python dependencies</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>CDP Mode: Patch 126 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4453">seleniumbase/SeleniumBase#4453</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.51.9...v4.51.10">https://github.com/seleniumbase/SeleniumBase/compare/v4.51.9...v4.51.10</a></p>
<h2>4.51.9 - CDP Mode: Patch 125</h2>
<h2>CDP Mode: Patch 125</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/9954e1df1c17fd33a95999a78e011506d3e7bcc5">Fix
non-authenticated proxy in the sb_cdp format</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/35bad5b60b1f47a54a129d8aeb01ac0dcabadccd">Refresh
Python dependencies</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/2bac11ba394f8edff0fff591f34400c946c01c62">Update
CDP Mode examples</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>CDP Mode: Patch 125 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4450">seleniumbase/SeleniumBase#4450</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.51.8...v4.51.9">https://github.com/seleniumbase/SeleniumBase/compare/v4.51.8...v4.51.9</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/85ca8c17da2bb85ca584c19ccc1773f64f0e1f8d"><code>85ca8c1</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4457">#4457</a>
from seleniumbase/cdp-mode-patch-127</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/e5c180747c8fdc7da7990f6c8cf78fc166b5d801"><code>e5c1807</code></a>
Version 4.51.11</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/710ae0abd46732bd145e578b6a22fb5eecde27b9"><code>710ae0a</code></a>
Update examples</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/8372dbc85d60c504c678a738df648791d712aec1"><code>8372dbc</code></a>
Refresh Python dependencies</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/3ba187938eca01c8582c0c832b5d00c44105b96f"><code>3ba1879</code></a>
Fix memory leaks, fix bugs, and update timing</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/58995fc57a3bc2e6169fde7a4b210c557d768e52"><code>58995fc</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4453">#4453</a>
from seleniumbase/cdp-mode-patch-126</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/9d98315c3989a4739d6e1b2aa1da7d0cb8a56732"><code>9d98315</code></a>
Version 4.51.10</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/b050714d848b370bd7500acfc01010607b6cb6d3"><code>b050714</code></a>
Update CDP Mode examples</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/0455ee62040c1b5185430035fd851518a9c83b84"><code>0455ee6</code></a>
Refresh Python dependencies</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/076956c14a944b25ff191189b32123220e490fff"><code>076956c</code></a>
Update timing of stealthy CDP Mode clicks</li>
<li>Additional commits viewable in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.51.8...v4.51.11">compare
view</a></li>
</ul>
</details>
<br />

Updates `prek` from 0.4.11 to 0.4.12
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/releases">prek's
releases</a>.</em></p>
<blockquote>
<h2>0.4.12</h2>
<h2>Release Notes</h2>
<p>Released on 2026-08-03.</p>
<h3>Enhancements</h3>
<ul>
<li>Add <code>--require-group</code> for hook group intersections (<a
href="https://redirect.github.com/j178/prek/pull/2472">#2472</a>)</li>
<li>Align fast-path and builtin pre-commit hooks (<a
href="https://redirect.github.com/j178/prek/pull/2433">#2433</a>)</li>
<li>Do not shuffle file list for verbose output (<a
href="https://redirect.github.com/j178/prek/pull/2431">#2431</a>)</li>
<li>Improve top-level command descriptions (<a
href="https://redirect.github.com/j178/prek/pull/2429">#2429</a>)</li>
<li>Install <code>uv</code> from Astral CDN and drop source racing (<a
href="https://redirect.github.com/j178/prek/pull/2455">#2455</a>)</li>
<li>Make <code>prek install --force</code> bypass external hooks paths
(<a
href="https://redirect.github.com/j178/prek/pull/2437">#2437</a>)</li>
<li>Show builtin hook flags in verbose list output (<a
href="https://redirect.github.com/j178/prek/pull/2427">#2427</a>)</li>
<li>Verify uv release archive checksums (<a
href="https://redirect.github.com/j178/prek/pull/2456">#2456</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Precompute file tags in parallel (<a
href="https://redirect.github.com/j178/prek/pull/2440">#2440</a>)</li>
<li>Skip diffs after known hook modifications (<a
href="https://redirect.github.com/j178/prek/pull/2447">#2447</a>)</li>
<li>Skip worktree diffs for read-only languages (<a
href="https://redirect.github.com/j178/prek/pull/2432">#2432</a>)</li>
<li>Track builtin hook file changes directly (<a
href="https://redirect.github.com/j178/prek/pull/2404">#2404</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Use full object IDs in diff snapshots (<a
href="https://redirect.github.com/j178/prek/pull/2448">#2448</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Add a multi-repository configuration example (<a
href="https://redirect.github.com/j178/prek/pull/2434">#2434</a>)</li>
<li>Rewrite benchmark documentation (<a
href="https://redirect.github.com/j178/prek/pull/2469">#2469</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/j178"><code>@​j178</code></a></li>
<li><a
href="https://github.com/BitWeaverDev"><code>@​BitWeaverDev</code></a></li>
<li><a
href="https://github.com/allanlewis"><code>@​allanlewis</code></a></li>
</ul>
<h2>Install prek 0.4.12</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://github.com/j178/prek/releases/download/v0.4.12/prek-installer.sh
| sh
</code></pre>
<h3>Install prebuilt binaries via powershell script</h3>
<pre lang="sh"><code>powershell -ExecutionPolicy Bypass -c &quot;irm
https://github.com/j178/prek/releases/download/v0.4.12/prek-installer.ps1
| iex&quot;
&lt;/tr&gt;&lt;/table&gt; 
</code></pre>
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/blob/master/CHANGELOG.md">prek's
changelog</a>.</em></p>
<blockquote>
<h2>0.4.12</h2>
<p>Released on 2026-08-03.</p>
<h3>Enhancements</h3>
<ul>
<li>Add <code>--require-group</code> for hook group intersections (<a
href="https://redirect.github.com/j178/prek/pull/2472">#2472</a>)</li>
<li>Align fast-path and builtin pre-commit hooks (<a
href="https://redirect.github.com/j178/prek/pull/2433">#2433</a>)</li>
<li>Do not shuffle file list for verbose output (<a
href="https://redirect.github.com/j178/prek/pull/2431">#2431</a>)</li>
<li>Improve top-level command descriptions (<a
href="https://redirect.github.com/j178/prek/pull/2429">#2429</a>)</li>
<li>Install <code>uv</code> from Astral CDN and drop source racing (<a
href="https://redirect.github.com/j178/prek/pull/2455">#2455</a>)</li>
<li>Make <code>prek install --force</code> bypass external hooks paths
(<a
href="https://redirect.github.com/j178/prek/pull/2437">#2437</a>)</li>
<li>Show builtin hook flags in verbose list output (<a
href="https://redirect.github.com/j178/prek/pull/2427">#2427</a>)</li>
<li>Verify uv release archive checksums (<a
href="https://redirect.github.com/j178/prek/pull/2456">#2456</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Precompute file tags in parallel (<a
href="https://redirect.github.com/j178/prek/pull/2440">#2440</a>)</li>
<li>Skip diffs after known hook modifications (<a
href="https://redirect.github.com/j178/prek/pull/2447">#2447</a>)</li>
<li>Skip worktree diffs for read-only languages (<a
href="https://redirect.github.com/j178/prek/pull/2432">#2432</a>)</li>
<li>Track builtin hook file changes directly (<a
href="https://redirect.github.com/j178/prek/pull/2404">#2404</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Use full object IDs in diff snapshots (<a
href="https://redirect.github.com/j178/prek/pull/2448">#2448</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Add a multi-repository configuration example (<a
href="https://redirect.github.com/j178/prek/pull/2434">#2434</a>)</li>
<li>Rewrite benchmark documentation (<a
href="https://redirect.github.com/j178/prek/pull/2469">#2469</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/j178"><code>@​j178</code></a></li>
<li><a
href="https://github.com/BitWeaverDev"><code>@​BitWeaverDev</code></a></li>
<li><a
href="https://github.com/allanlewis"><code>@​allanlewis</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/j178/prek/commit/728238cf01e7b9e9ee8d1457f20c772fdc8e8426"><code>728238c</code></a>
Bump version to 0.4.12 (<a
href="https://redirect.github.com/j178/prek/issues/2473">#2473</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/e5403ca77cf44eb2d285328f0dd63386efddbddd"><code>e5403ca</code></a>
Add <code>--require-group</code> for hook group intersections (<a
href="https://redirect.github.com/j178/prek/issues/2472">#2472</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/370fc41b6b520b5fa0e34deb80d6415fc4417f7d"><code>370fc41</code></a>
Use ssl-certs to load custom certificates (<a
href="https://redirect.github.com/j178/prek/issues/2470">#2470</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/7cc64cac5f95c71b152406dd63658499ede597fe"><code>7cc64ca</code></a>
Rewrite benchmark documentation (<a
href="https://redirect.github.com/j178/prek/issues/2469">#2469</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/6a81c105d682e48bd5cff68533cf66ec150f19c1"><code>6a81c10</code></a>
Update ubuntu Docker tag to v26 (<a
href="https://redirect.github.com/j178/prek/issues/2466">#2466</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/500a9d4149febbe639b8ac01fe1f8065ba5204ec"><code>500a9d4</code></a>
Run Conda tests sequentially (<a
href="https://redirect.github.com/j178/prek/issues/2468">#2468</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/ebc1c38f41a44995394fb2c0f4ba971763d23be5"><code>ebc1c38</code></a>
Run .NET tests sequentially on Unix (<a
href="https://redirect.github.com/j178/prek/issues/2467">#2467</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/88fec177545361e7b2618b84defc8d24195ad048"><code>88fec17</code></a>
Update Rust crate tokio-util to v0.7.19 (<a
href="https://redirect.github.com/j178/prek/issues/2463">#2463</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/9d9c9f504a62b78c6b2154273d635b080c69e625"><code>9d9c9f5</code></a>
Use full object IDs in diff snapshots (<a
href="https://redirect.github.com/j178/prek/issues/2448">#2448</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/0c7fc8474377fde1811eec879eb0a42010129d80"><code>0c7fc84</code></a>
Update Rust crate tokio to v1.53.1 (<a
href="https://redirect.github.com/j178/prek/issues/2462">#2462</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/j178/prek/compare/v0.4.11...v0.4.12">compare
view</a></li>
</ul>
</details>
<br />

Updates `ruff` from 0.16.1 to 0.16.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/releases">ruff's
releases</a>.</em></p>
<blockquote>
<h2>0.16.2</h2>
<h2>Release Notes</h2>
<p>Released on 2026-08-06.</p>
<h3>Bug fixes</h3>
<ul>
<li>[<code>flake8-pyi</code>] Avoid false positives on
<code>singledispatch</code> functions (<code>PYI041</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27335">#27335</a>)</li>
</ul>
<h3>Server</h3>
<ul>
<li>Register formatting capabilities dynamically to exclude TOML files
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/27332">#27332</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/MeGaGiGaGon"><code>@​MeGaGiGaGon</code></a></li>
<li><a
href="https://github.com/charliermarsh"><code>@​charliermarsh</code></a></li>
<li><a href="https://github.com/epage"><code>@​epage</code></a></li>
<li><a href="https://github.com/sharkdp"><code>@​sharkdp</code></a></li>
<li><a href="https://github.com/ntBre"><code>@​ntBre</code></a></li>
</ul>
<h2>Install ruff 0.16.2</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-installer.sh
| sh
</code></pre>
<h3>Install prebuilt binaries via powershell script</h3>
<pre lang="sh"><code>powershell -ExecutionPolicy Bypass -c &quot;irm
https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-installer.ps1
| iex&quot;
</code></pre>
<h2>Download ruff 0.16.2</h2>
<table>
<thead>
<tr>
<th>File</th>
<th>Platform</th>
<th>Checksum</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-aarch64-apple-darwin.tar.gz">ruff-aarch64-apple-darwin.tar.gz</a></td>
<td>Apple Silicon macOS</td>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-aarch64-apple-darwin.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-x86_64-apple-darwin.tar.gz">ruff-x86_64-apple-darwin.tar.gz</a></td>
<td>Intel macOS</td>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-x86_64-apple-darwin.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-aarch64-pc-windows-msvc.zip">ruff-aarch64-pc-windows-msvc.zip</a></td>
<td>ARM64 Windows</td>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-aarch64-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-i686-pc-windows-msvc.zip">ruff-i686-pc-windows-msvc.zip</a></td>
<td>x86 Windows</td>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-i686-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-x86_64-pc-windows-msvc.zip">ruff-x86_64-pc-windows-msvc.zip</a></td>
<td>x64 Windows</td>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-x86_64-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-aarch64-unknown-linux-gnu.tar.gz">ruff-aarch64-unknown-linux-gnu.tar.gz</a></td>
<td>ARM64 Linux</td>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-aarch64-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-i686-unknown-linux-gnu.tar.gz">ruff-i686-unknown-linux-gnu.tar.gz</a></td>
<td>x86 Linux</td>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-i686-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-powerpc64-unknown-linux-gnu.tar.gz">ruff-powerpc64-unknown-linux-gnu.tar.gz</a></td>
<td>PPC64 Linux</td>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-powerpc64-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-powerpc64le-unknown-linux-gnu.tar.gz">ruff-powerpc64le-unknown-linux-gnu.tar.gz</a></td>
<td>PPC64LE Linux</td>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-powerpc64le-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-riscv64gc-unknown-linux-gnu.tar.gz">ruff-riscv64gc-unknown-linux-gnu.tar.gz</a></td>
<td>RISCV Linux</td>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-riscv64gc-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-s390x-unknown-linux-gnu.tar.gz">ruff-s390x-unknown-linux-gnu.tar.gz</a></td>
<td>S390x Linux</td>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.16.2/ruff-s390x-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
</tbody>
</table>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's
changelog</a>.</em></p>
<blockquote>
<h2>0.16.2</h2>
<p>Released on 2026-08-06.</p>
<h3>Bug fixes</h3>
<ul>
<li>[<code>flake8-pyi</code>] Avoid false positives on
<code>singledispatch</code> functions (<code>PYI041</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27335">#27335</a>)</li>
</ul>
<h3>Server</h3>
<ul>
<li>Register formatting capabilities dynamically to exclude TOML files
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/27332">#27332</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/MeGaGiGaGon"><code>@​MeGaGiGaGon</code></a></li>
<li><a
href="https://github.com/charliermarsh"><code>@​charliermarsh</code></a></li>
<li><a href="https://github.com/epage"><code>@​epage</code></a></li>
<li><a href="https://github.com/sharkdp"><code>@​sharkdp</code></a></li>
<li><a href="https://github.com/ntBre"><code>@​ntBre</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ruff/commit/5b48a040974781ba90b47c8df628f8fd9b6c95dd"><code>5b48a04</code></a>
Bump 0.16.2 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27555">#27555</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/1b9e5fc483b95a01fe02ff104820280b1b32e8ae"><code>1b9e5fc</code></a>
Update Swatinem/rust-cache action to v2.9.2 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27568">#27568</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/c4e86fc0394c92a9334ba2eb026c77c21db403be"><code>c4e86fc</code></a>
[ty] Add helper extension methods for half-range and equality
constraints (<a
href="https://redirect.github.com/astral-sh/ruff/issues/2">#2</a>...</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/17a00de2e298612201a8fe30790e9399204af1b9"><code>17a00de</code></a>
[ty] Reuse primer commands in memory reports (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27553">#27553</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/6ea296b96923e142eb13af2bc6ad261c280d8eb1"><code>6ea296b</code></a>
[ty] Normalize type labels in structured docstrings (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26923">#26923</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/2fc445f0053f4ec27c717fae0de3671d73c103be"><code>2fc445f</code></a>
[ty] Diagnose invalid <strong>getattr</strong> calls (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27502">#27502</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/22c7823c4e8bffcca97688d8438c9b567d6817d8"><code>22c7823</code></a>
[ty] Enable (but downrank) auto-import completion suggestions from
stub-only ...</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/05160d507f05345a72db9c28ab4edf7c92334819"><code>05160d5</code></a>
[ty] Diagnose invalid descriptor <code>__get__</code> calls (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27400">#27400</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/baea3d0dcec6d6f6d1659321940f3725771c5f45"><code>baea3d0</code></a>
[ty] Expose strict analysis options in the playground (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27543">#27543</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/c88946ebeb92be6d276087f0d528cd6471df4ead"><code>c88946e</code></a>
[ty] Bump ecosystem-analyzer for strict project settings (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27542">#27542</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ruff/compare/0.16.1...0.16.2">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 01:06:19 -04:00
Guflly 3c51b7cfaa Fix OIDC redirects on custom ports (#1180)
Fixes #1175

Preserves the forwarded host and port when Shelfmark builds OIDC
callback URLs. The reverse-proxy examples now retain custom ports as
well.

Tests:
- `uv run pytest -n 0 -q tests/core/test_proxy_headers.py
tests/core/test_oidc_routes.py`
- `uv run ruff check shelfmark/main.py tests/core/test_proxy_headers.py`
- `uv run ruff format --check shelfmark/main.py
tests/core/test_proxy_headers.py`
2026-08-11 01:05:23 -04:00
FlyinPancakeandCopilot Autofix powered by AI 3a9cff9816 feat(metadata): add Moly.hu metadata provider (#1172)
First of all, I don't know if you even want to merge a scraper-based
metadata provider. I made this just for my use-case. If you'd rather
not, I completely understand it.

An alternative would be adopting [Audiobookshelf's Metadata Provider
API](https://audiobookshelf.org/docs/documentation/community/community-providers)
which I contributed to it for exactly the reason to not have scrapers.

## What

Adds [Moly.hu](https://moly.hu) — the Hungarian community book catalog —
as a metadata provider, following the existing provider plugin
architecture (`@register_provider` + settings tab with enable checkbox
and Test Connection button, disabled by default).

## Why

None of the current providers cover Hungarian editions well: Hardcover
and Open Library rarely index them, and Google Books coverage is spotty.
Moly.hu is the de-facto catalog for Hungarian books (local editions
*and* Hungarian translations of foreign works). With this provider,
Universal mode works end-to-end for Hungarian titles: moly search →
localized title/author feed the release search → indexers that carry
Hungarian content can actually match. Related pain points: #595 (books
missing from metadata providers), #1035 (interest in niche sources).

## How

- HTML scraping with BeautifulSoup (already a dependency), no API key
needed
- Scraping approach (search URL, page structure, language-tag mapping)
adapted from the long-lived Calibre `Moly_hu` plugin (GPL v3, credited
in the module docstring), with fallback selector chains inherited from
it
- Sliding-window rate limit (30 req/min) to stay polite to a small
community site
- Standard `@cacheable` decorators; fetch failures return `None` so they
are not cached (same behavior as the Google Books provider)
- Search results carry cover thumbnails, rating and series info as
display fields; `get_book` parses title (zero-width chars stripped,
nested series link excluded), authors, ISBN-13/10, publisher, publish
year, description (spoiler-warning prefix stripped), tags/genres, cover,
and language (from moly's language tags, defaulting to `hu`)
- ISBN search resolves through moly's site search

## Testing

- `tests/metadata/test_moly_parse.py`: offline tests with fixture HTML
mirroring live moly.hu markup — search parsing/dedup, pagination guard,
failure-not-cached behavior, book-page parsing, ISBN resolution, ISBN
validation helper
- `uv run pytest tests/metadata` green (45 passed), `ruff check` / `ruff
format` clean
- Verified live against moly.hu (search, get_book, ISBN lookup) and
running in Docker alongside Hardcover

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-11 00:46:32 -04:00
dependabot[bot] 453d1f2b56 Bump oxlint-tsgolint from 0.25.0 to 7.0.2001 in /src/frontend (#1156)
Bumps [oxlint-tsgolint](https://github.com/oxc-project/tsgolint) from
0.25.0 to 7.0.2001.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/tsgolint/releases">oxlint-tsgolint's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.2001</h2>
<h2>Highlights</h2>
<p><a
href="https://oxc.rs/blog/2026-07-22-type-aware-linting-stable">Announcing
Type-Aware Linting Stable</a></p>
<h2>What's Changed</h2>
<ul>
<li>fix(release): preserve binary executable permissions by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1104">oxc-project/tsgolint#1104</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/oxc-project/tsgolint/compare/v7.0.2000...v7.0.2001">https://github.com/oxc-project/tsgolint/compare/v7.0.2000...v7.0.2001</a></p>
<h2>v7.0.2000</h2>
<h2>What's Changed</h2>
<ul>
<li>feat(no-unnecessary-condition): improve diagnostics by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1092">oxc-project/tsgolint#1092</a></li>
<li>ci: use pnpm publish and drop global npm install by <a
href="https://github.com/Boshen"><code>@​Boshen</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1095">oxc-project/tsgolint#1095</a></li>
<li>feat(no-floating-promises): improve diagnostics by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1093">oxc-project/tsgolint#1093</a></li>
<li>fix(no-unnecessary-type-assertion): preserve type-changing
assertions by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1097">oxc-project/tsgolint#1097</a></li>
<li>chore(deps): update github actions by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1098">oxc-project/tsgolint#1098</a></li>
<li>chore(deps): update module github.com/dlclark/regexp2/v2 to v2.5.2
by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1099">oxc-project/tsgolint#1099</a></li>
<li>chore(deps): update npm packages by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1100">oxc-project/tsgolint#1100</a></li>
<li>fix(ci): disable pnpm git checks for generated release packages by
<a href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1103">oxc-project/tsgolint#1103</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/oxc-project/tsgolint/compare/v0.25.0...v7.0.2000">https://github.com/oxc-project/tsgolint/compare/v0.25.0...v7.0.2000</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/482dcf70bffce7ea56f63128c74beb67dec658a2"><code>482dcf7</code></a>
fix(release): preserve binary executable permissions (<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1104">#1104</a>)</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/7ea9b74861b5249be86aa19f5d44518e1b2554be"><code>7ea9b74</code></a>
fix(ci): disable pnpm git checks for generated release packages (<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1103">#1103</a>)</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/a90133b97edcda3458445c5bd676c6d20db74042"><code>a90133b</code></a>
chore(deps): update npm packages (<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1100">#1100</a>)</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/f5a7520fe69ec59cc84efd096de9f5d66cda13c4"><code>f5a7520</code></a>
chore(deps): update module github.com/dlclark/regexp2/v2 to v2.5.2 (<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1099">#1099</a>)</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/5cc766e7df4d9da6a12970c7788a65c55329b635"><code>5cc766e</code></a>
chore(deps): update github actions (<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1098">#1098</a>)</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/4e9120aaefad311b2f959de053ce468967d06190"><code>4e9120a</code></a>
fix(no-unnecessary-type-assertion): preserve type-changing assertions
(<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1097">#1097</a>)</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/bbcafc7b10c33c092507f97b8f76488d5c79d9ba"><code>bbcafc7</code></a>
feat(no-floating-promises): improve diagnostics (<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1093">#1093</a>)</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/305e137c155f012ddc89a9d62aab4866e09a8114"><code>305e137</code></a>
ci: use pnpm publish and drop global npm install (<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1095">#1095</a>)</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/1b97ad9b435269f251876a4f2932e494f6cc0127"><code>1b97ad9</code></a>
feat(no-unnecessary-condition): improve diagnostics (<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1092">#1092</a>)</li>
<li>See full diff in <a
href="https://github.com/oxc-project/tsgolint/compare/v0.25.0...v7.0.2001">compare
view</a></li>
</ul>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-05 09:25:21 -04:00
dependabot[bot] db11da5bda Bump the python-deps group across 1 directory with 2 updates (#1163)
Bumps the python-deps group with 2 updates in the / directory:
[qbittorrent-api](https://github.com/rmartin16/qbittorrent-api) and
[ruff](https://github.com/astral-sh/ruff).

Updates `qbittorrent-api` from 2026.7.0 to 2026.8.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/rmartin16/qbittorrent-api/releases">qbittorrent-api's
releases</a>.</em></p>
<blockquote>
<h2>release-2026.8.0</h2>
<h2>What's Changed</h2>
<h3>Fixes</h3>
<ul>
<li>fix: preserve cached client when slicing or copying List objects by
<a href="https://github.com/Sanjays2402"><code>@​Sanjays2402</code></a>
in <a
href="https://redirect.github.com/rmartin16/qbittorrent-api/pull/644">rmartin16/qbittorrent-api#644</a></li>
</ul>
<h3>CI</h3>
<ul>
<li>ignore conflict409 on adding torrents in tests by <a
href="https://github.com/rmartin16"><code>@​rmartin16</code></a> in <a
href="https://redirect.github.com/rmartin16/qbittorrent-api/pull/640">rmartin16/qbittorrent-api#640</a></li>
<li>make CI tests resilient to qBittorrent's eventual consistency by <a
href="https://github.com/rmartin16"><code>@​rmartin16</code></a> in <a
href="https://redirect.github.com/rmartin16/qbittorrent-api/pull/643">rmartin16/qbittorrent-api#643</a></li>
</ul>
<h3>Chores</h3>
<ul>
<li>Bump the github-actions group with 4 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/rmartin16/qbittorrent-api/pull/647">rmartin16/qbittorrent-api#647</a></li>
<li>Bump pre-commit hook for ruff-pre-commit from v0.15.20 to v0.16.1 by
<a href="https://github.com/rmartin16"><code>@​rmartin16</code></a> in
<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/pull/649">rmartin16/qbittorrent-api#649</a></li>
<li>Bump pre-commit hook for mirrors-mypy from v2.1.0 to v2.3.0 by <a
href="https://github.com/rmartin16"><code>@​rmartin16</code></a> in <a
href="https://redirect.github.com/rmartin16/qbittorrent-api/pull/650">rmartin16/qbittorrent-api#650</a></li>
<li>Bump the uv group with 7 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/rmartin16/qbittorrent-api/pull/648">rmartin16/qbittorrent-api#648</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/Sanjays2402"><code>@​Sanjays2402</code></a>
made their first contribution in <a
href="https://redirect.github.com/rmartin16/qbittorrent-api/pull/644">rmartin16/qbittorrent-api#644</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/rmartin16/qbittorrent-api/compare/v2026.7.0...v2026.8.0">https://github.com/rmartin16/qbittorrent-api/compare/v2026.7.0...v2026.8.0</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/rmartin16/qbittorrent-api/blob/main/CHANGELOG.md">qbittorrent-api's
changelog</a>.</em></p>
<blockquote>
<h3>v2026.8.0 (01 aug 2026)</h3>
<ul>
<li>Fix slicing returned objects (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/644">#644</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/1c9f4f72367547e782e58a61be65c4db9df08006"><code>1c9f4f7</code></a>
bump to v2026.8.0</li>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/ca122da2b5af3bdd6434471072b15070380c282d"><code>ca122da</code></a>
fix: preserve cached client when slicing or copying List objects (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/644">#644</a>)</li>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/4b3cab11c7090e32e7341b7144d3dbcce933b07f"><code>4b3cab1</code></a>
Bump the uv group with 7 updates (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/648">#648</a>)</li>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/1bbc79b928ef11bec32360d17800695916a37c2a"><code>1bbc79b</code></a>
Bumped pre-commit hook for mirrors-mypy from v2.1.0 to v2.3.0 (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/650">#650</a>)</li>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/97897ea6c39a1e86617212fdb073aa6e71efe53f"><code>97897ea</code></a>
Bumped pre-commit hook for ruff-pre-commit from v0.15.20 to v0.16.1 (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/649">#649</a>)</li>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/27ef5fa77a518d80a78a51d0d648c4af5aa75c3b"><code>27ef5fa</code></a>
Bump the github-actions group with 4 updates (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/647">#647</a>)</li>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/f76602bf71d302ae7e180d2d47b1c840f94eb1d8"><code>f76602b</code></a>
make CI tests resilient to qBittorrent's eventual consistency (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/643">#643</a>)</li>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/3006f83fa51abc9c5e3fadeff42e4c1ecd82e91c"><code>3006f83</code></a>
ignore conflict409 on adding torrents in tests (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/640">#640</a>)</li>
<li>See full diff in <a
href="https://github.com/rmartin16/qbittorrent-api/compare/v2026.7.0...v2026.8.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `ruff` from 0.16.0 to 0.16.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/releases">ruff's
releases</a>.</em></p>
<blockquote>
<h2>0.16.1</h2>
<h2>Release Notes</h2>
<p>Released on 2026-07-30.</p>
<h3>Preview features</h3>
<ul>
<li>Add an option to opt out of human-readable names (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27160">#27160</a>)</li>
<li>[<code>flake8-pytest-style</code>] Make fixes safe by default and
unsafe only when comments are present (<code>PT018</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27201">#27201</a>)</li>
<li>[<code>pyupgrade</code>] Skip fix when a defaulted
<code>TypeVar</code> precedes a non-defaulted one (<code>UP040</code>,
<code>UP046</code>, <code>UP047</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27133">#27133</a>)</li>
<li>[<code>ruff</code>] Fix false positive with unpacked arguments
(<code>RUF065</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26959">#26959</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Bump <code>gen-lsp-types</code> to gracefully handle unknown
enumeration values in LSP messages (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27230">#27230</a>)</li>
<li>[<code>flake8-bugbear</code>] Mark <code>range</code> as immutable
(<code>B008</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27247">#27247</a>)</li>
<li>[<code>flake8-comprehensions</code>] NFKC-normalize keyword names in
<code>C408</code> fix (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26813">#26813</a>)</li>
<li>[<code>flake8-return</code>] Fix false positive when variable is
read in <code>finally</code> clause (<code>RET504</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25441">#25441</a>)</li>
<li>[<code>pydocstyle</code>] Skip section detection inside RST
directive bodies (<code>D214</code>, <code>D405</code>,
<code>D413</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23635">#23635</a>)</li>
<li>[<code>refurb</code>] Parenthesize <code>yield</code> arguments in
the <code>FURB192</code> fix (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27192">#27192</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>flake8-pytest-style</code>] Mark <code>PT022</code> fixes as
unsafe (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26440">#26440</a>)</li>
<li>[<code>refurb</code>] Mark fixes that remove unknown separators as
unsafe (<code>FURB105</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27200">#27200</a>)</li>
</ul>
<h3>Server</h3>
<ul>
<li>Fix indexing of excluded nested Ruff workspaces (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27303">#27303</a>)</li>
<li>Lint TOML files in the LSP (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26862">#26862</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Cover <code>pycon</code> Markdown formatting (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27153">#27153</a>)</li>
<li>[<code>flake8-bandit</code>] Document <code>TYPE_CHECKING</code>
exception (<code>S101</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27004">#27004</a>)</li>
<li>[<code>flake8-import-conventions</code>] Document that
<code>extend-aliases</code> can override default aliases (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27191">#27191</a>)</li>
<li>[<code>pylint</code>] Add missing fix safety gotchas for
<code>non-augmented-assignment</code> (<code>PLR6104</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27250">#27250</a>)</li>
</ul>
<h3>Other changes</h3>
<ul>
<li>Reduce syntax error noise by swallowing dedents like indents (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27170">#27170</a>)</li>
<li>Vendor latest annotate-snippets (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27033">#27033</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/bxff"><code>@​bxff</code></a></li>
<li><a
href="https://github.com/anishgirianish"><code>@​anishgirianish</code></a></li>
<li><a href="https://github.com/Avasam"><code>@​Avasam</code></a></li>
<li><a href="https://github.com/epage"><code>@​epage</code></a></li>
<li><a href="https://github.com/LHMQ878"><code>@​LHMQ878</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's
changelog</a>.</em></p>
<blockquote>
<h2>0.16.1</h2>
<p>Released on 2026-07-30.</p>
<h3>Preview features</h3>
<ul>
<li>Add an option to opt out of human-readable names (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27160">#27160</a>)</li>
<li>[<code>flake8-pytest-style</code>] Make fixes safe by default and
unsafe only when comments are present (<code>PT018</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27201">#27201</a>)</li>
<li>[<code>pyupgrade</code>] Skip fix when a defaulted
<code>TypeVar</code> precedes a non-defaulted one (<code>UP040</code>,
<code>UP046</code>, <code>UP047</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27133">#27133</a>)</li>
<li>[<code>ruff</code>] Fix false positive with unpacked arguments
(<code>RUF065</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26959">#26959</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Bump <code>gen-lsp-types</code> to gracefully handle unknown
enumeration values in LSP messages (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27230">#27230</a>)</li>
<li>[<code>flake8-bugbear</code>] Mark <code>range</code> as immutable
(<code>B008</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27247">#27247</a>)</li>
<li>[<code>flake8-comprehensions</code>] NFKC-normalize keyword names in
<code>C408</code> fix (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26813">#26813</a>)</li>
<li>[<code>flake8-return</code>] Fix false positive when variable is
read in <code>finally</code> clause (<code>RET504</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25441">#25441</a>)</li>
<li>[<code>pydocstyle</code>] Skip section detection inside RST
directive bodies (<code>D214</code>, <code>D405</code>,
<code>D413</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23635">#23635</a>)</li>
<li>[<code>refurb</code>] Parenthesize <code>yield</code> arguments in
the <code>FURB192</code> fix (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27192">#27192</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>flake8-pytest-style</code>] Mark <code>PT022</code> fixes as
unsafe (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26440">#26440</a>)</li>
<li>[<code>refurb</code>] Mark fixes that remove unknown separators as
unsafe (<code>FURB105</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27200">#27200</a>)</li>
</ul>
<h3>Server</h3>
<ul>
<li>Fix indexing of excluded nested Ruff workspaces (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27303">#27303</a>)</li>
<li>Lint TOML files in the LSP (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26862">#26862</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Cover <code>pycon</code> Markdown formatting (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27153">#27153</a>)</li>
<li>[<code>flake8-bandit</code>] Document <code>TYPE_CHECKING</code>
exception (<code>S101</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27004">#27004</a>)</li>
<li>[<code>flake8-import-conventions</code>] Document that
<code>extend-aliases</code> can override default aliases (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27191">#27191</a>)</li>
<li>[<code>pylint</code>] Add missing fix safety gotchas for
<code>non-augmented-assignment</code> (<code>PLR6104</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27250">#27250</a>)</li>
</ul>
<h3>Other changes</h3>
<ul>
<li>Reduce syntax error noise by swallowing dedents like indents (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27170">#27170</a>)</li>
<li>Vendor latest annotate-snippets (<a
href="https://redirect.github.com/astral-sh/ruff/pull/27033">#27033</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/bxff"><code>@​bxff</code></a></li>
<li><a
href="https://github.com/anishgirianish"><code>@​anishgirianish</code></a></li>
<li><a href="https://github.com/Avasam"><code>@​Avasam</code></a></li>
<li><a href="https://github.com/epage"><code>@​epage</code></a></li>
<li><a href="https://github.com/LHMQ878"><code>@​LHMQ878</code></a></li>
<li><a
href="https://github.com/MichaReiser"><code>@​MichaReiser</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ruff/commit/80790b348b5188e7fc253665540f442c6ec7dd05"><code>80790b3</code></a>
Bump 0.16.1 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27330">#27330</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/63830f3e97b56ca3be0dd8f1092f76c4acc63213"><code>63830f3</code></a>
[ty] Borrow from constraint set storage less often (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27328">#27328</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/f40dca98a7f1b6fff0cee5ecf1e6364d5a8bdc15"><code>f40dca9</code></a>
[ty] Preserve forwarded expanded-variadic diagnostic sources (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27266">#27266</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/0d804975a2645d6e4a795cf501fd764b88aa47e3"><code>0d80497</code></a>
Lint TOML files in the LSP (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26862">#26862</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/d91586bd5b30f77f6614d57b73fc7dbea9051a0e"><code>d91586b</code></a>
Update prek dependencies (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27293">#27293</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/7da4b8b8d78fd6df2b3e06d8466d9cd49822900d"><code>7da4b8b</code></a>
[ty] Respect bounds and constraints in generic materializations (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27228">#27228</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/b20daf741241a6829ff8963a203f7847561deb62"><code>b20daf7</code></a>
[ty] refactor: add helper function to send partial results (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27249">#27249</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/4d4c8fa1c75b00561ea24eefefb13eb5ff80e01f"><code>4d4c8fa</code></a>
[ty] Emit diagnostic when specializing a non-generic class (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26883">#26883</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/7c3e2db97deb5c5edd6e4303403ef6caf2bde8be"><code>7c3e2db</code></a>
[ty] Fix enum class container assignability (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27318">#27318</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/d5ef97fcd03e108f7510f84b8ed85bb4051311fe"><code>d5ef97f</code></a>
[<code>flake8-return</code>] Fix false positive when variable is read in
<code>finally</code> claus...</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ruff/compare/0.16.0...0.16.1">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-05 09:23:43 -04:00
dependabot[bot] 0b22618d96 Bump the gh-actions group with 5 updates (#1157)
Bumps the gh-actions group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [docker/login-action](https://github.com/docker/login-action) |
`4.4.0` | `4.5.2` |
| [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) | `8.3.2`
| `9.0.0` |
| [github/codeql-action/init](https://github.com/github/codeql-action) |
`4.37.0` | `4.37.3` |
|
[github/codeql-action/autobuild](https://github.com/github/codeql-action)
| `4.37.0` | `4.37.3` |
|
[github/codeql-action/analyze](https://github.com/github/codeql-action)
| `4.37.0` | `4.37.3` |

Updates `docker/login-action` from 4.4.0 to 4.5.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/docker/login-action/releases">docker/login-action's
releases</a>.</em></p>
<blockquote>
<h2>v4.5.2</h2>
<ul>
<li>Surface Docker Hub OIDC error responses by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/login-action/pull/1058">docker/login-action#1058</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/login-action/compare/v4.5.1...v4.5.2">https://github.com/docker/login-action/compare/v4.5.1...v4.5.2</a></p>
<h2>v4.5.1</h2>
<ul>
<li>Support <code>dhi.io</code> as Docker Hub OIDC registry by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/login-action/pull/1054">docker/login-action#1054</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/login-action/compare/v4.5.0...v4.5.1">https://github.com/docker/login-action/compare/v4.5.0...v4.5.1</a></p>
<h2>v4.5.0</h2>
<ul>
<li><a href="https://github.com/docker/login-action#docker-hub">Docker
Hub OIDC</a> login support by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/login-action/pull/1048">docker/login-action#1048</a></li>
<li>Bump <code>@​aws-sdk/client-ecr</code> and
<code>@​aws-sdk/client-ecr-public</code> to 3.1091.0 in <a
href="https://redirect.github.com/docker/login-action/pull/1037">docker/login-action#1037</a></li>
<li>Bump <code>@​docker/actions-toolkit</code> from 0.92.0 to 0.94.0 in
<a
href="https://redirect.github.com/docker/login-action/pull/1044">docker/login-action#1044</a>
<a
href="https://redirect.github.com/docker/login-action/pull/1050">docker/login-action#1050</a></li>
<li>Bump brace-expansion from 1.1.13 to 1.1.16 in <a
href="https://redirect.github.com/docker/login-action/pull/1046">docker/login-action#1046</a></li>
<li>Bump js-yaml from 5.2.0 to 5.2.1 in <a
href="https://redirect.github.com/docker/login-action/pull/1038">docker/login-action#1038</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/login-action/compare/v4.4.0...v4.5.0">https://github.com/docker/login-action/compare/v4.4.0...v4.5.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/docker/login-action/commit/371161bbe7024a29a25c5e19bfcbc0804fe9ad2c"><code>371161b</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/1058">#1058</a>
from crazy-max/fix-dockerhub-oidc-error-handling</li>
<li><a
href="https://github.com/docker/login-action/commit/5dc73df38ebcfa6f96479901e253d172c3e35849"><code>5dc73df</code></a>
chore: update generated content</li>
<li><a
href="https://github.com/docker/login-action/commit/2aa1edee0b06c23880529064a4f7d7d3d2f9bc87"><code>2aa1ede</code></a>
surface Docker Hub OIDC error responses</li>
<li><a
href="https://github.com/docker/login-action/commit/abd2ef45e78c5afb21d64d4ca52ee8550d9572c7"><code>abd2ef4</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/1055">#1055</a>
from crazy-max/test-registry-auth-oidc</li>
<li><a
href="https://github.com/docker/login-action/commit/d49d3a9839fef51322fa44989a44fdc43fccfc22"><code>d49d3a9</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/1054">#1054</a>
from crazy-max/oidc-missing-dhi</li>
<li><a
href="https://github.com/docker/login-action/commit/b58b17c30b4db92a4ed049b213cae512b12e460b"><code>b58b17c</code></a>
test: cover Docker Hub OIDC with registry-auth</li>
<li><a
href="https://github.com/docker/login-action/commit/be646c21cec26cea303e29290d5f6ba6fde8e606"><code>be646c2</code></a>
chore: update generated content</li>
<li><a
href="https://github.com/docker/login-action/commit/d77c059cb9956cedaa427dc022d89f39acba678f"><code>d77c059</code></a>
support dhi.io as Docker Hub OIDC registry</li>
<li><a
href="https://github.com/docker/login-action/commit/06fb636fac595d6fb4b28a5dfcb21a6f5091859c"><code>06fb636</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/1037">#1037</a>
from docker/dependabot/npm_and_yarn/aws-sdk-dependen...</li>
<li><a
href="https://github.com/docker/login-action/commit/a8bc9539118a762b0e5788b53a50907977cc1b8d"><code>a8bc953</code></a>
[dependabot skip] chore: update generated content</li>
<li>Additional commits viewable in <a
href="https://github.com/docker/login-action/compare/af1e73f918a031802d376d3c8bbc3fe56130a9b0...371161bbe7024a29a25c5e19bfcbc0804fe9ad2c">compare
view</a></li>
</ul>
</details>
<br />

Updates `astral-sh/setup-uv` from 8.3.2 to 9.0.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/setup-uv/releases">astral-sh/setup-uv's
releases</a>.</em></p>
<blockquote>
<h2>v9.0.0 🌈 Change <code>prune-cache</code> default to
<code>false</code></h2>
<h2>Changes</h2>
<p>This release disables the default cache cache pruning to ease the
load on the PyPi infrastructure.
Since users might experience more GitHub Actions cache usage which might
result in higher costs this is marked as a breaking change. To read more
on why we did this (now) you can read the detailed analysis and
reasoning in <a
href="https://redirect.github.com/astral-sh/setup-uv/issues/967">#967</a></p>
<p>Besides this big breaking change we also have a small bugfix while
building caches for linux distributions that behave a big different than
the &quot;big ones&quot; and a speed up in version resolution by only
reading the version manifest until a matching version is found saving
runtime and network bandwith.</p>
<h2>🚨 Breaking changes</h2>
<ul>
<li>Change <code>prune-cache</code> default to <code>false</code> <a
href="https://github.com/charliermarsh"><code>@​charliermarsh</code></a>
(<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/967">#967</a>)</li>
</ul>
<h2>🐛 Bug fixes</h2>
<ul>
<li>fix: fall back to distribution ID when os-release has no version
field <a href="https://github.com/cxzhong"><code>@​cxzhong</code></a>
(<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/961">#961</a>)</li>
</ul>
<h2>🚀 Enhancements</h2>
<ul>
<li>Speed up version client by partial response reads <a
href="https://github.com/eifinger"><code>@​eifinger</code></a> (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/807">#807</a>)</li>
</ul>
<h2>🧰 Maintenance</h2>
<ul>
<li>chore: update known checksums for 0.11.30 @<a
href="https://github.com/apps/github-actions">github-actions[bot]</a>
(<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/968">#968</a>)</li>
<li>chore: update known checksums for 0.11.29 @<a
href="https://github.com/apps/github-actions">github-actions[bot]</a>
(<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/960">#960</a>)</li>
</ul>
<h2>📚 Documentation</h2>
<ul>
<li>docs: update version references to v8.3.2 @<a
href="https://github.com/apps/github-actions">github-actions[bot]</a>
(<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/949">#949</a>)</li>
</ul>
<h2>⬆️ Dependency updates</h2>
<ul>
<li>chore(deps): roll up Dependabot updates <a
href="https://github.com/eifinger"><code>@​eifinger</code></a> (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/970">#970</a>)</li>
<li>chore(deps): roll up Dependabot updates <a
href="https://github.com/eifinger"><code>@​eifinger</code></a> (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/962">#962</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/c771a70e6277c0a99b617c7a806ffedaca235ff9"><code>c771a70</code></a>
chore(deps): roll up Dependabot updates (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/970">#970</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/2f537ca87c1ffa233ca2a1b84815388e3e42d845"><code>2f537ca</code></a>
chore: update known checksums for 0.11.30 (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/968">#968</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/2269552d547df6f50e57442326930d30d943afe3"><code>2269552</code></a>
Speed up version client by partial response reads (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/807">#807</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/47a7f4fb2e900d6c33a5b5f231fa21dbfaeba52f"><code>47a7f4f</code></a>
Change <code>prune-cache</code> default to <code>false</code> (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/967">#967</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/71966eff34a27b0a62ed4b9f6f6e383e071b1bb5"><code>71966ef</code></a>
chore(deps): roll up Dependabot updates (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/962">#962</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/f12b1f0a84bd6dc2331b36b2bbdbb1d1e617dbcc"><code>f12b1f0</code></a>
fix: fall back to distribution ID when os-release has no version field
(<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/961">#961</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/ecd24dd710f2fb0dca1693a67af11fc4a5c5ec84"><code>ecd24dd</code></a>
chore: update known checksums for 0.11.29 (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/960">#960</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/6a191366842ac1502ba6c07e9b5acd5c2d9d8db3"><code>6a19136</code></a>
docs: update version references to v8.3.2 (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/949">#949</a>)</li>
<li>See full diff in <a
href="https://github.com/astral-sh/setup-uv/compare/11f9893b081a58869d3b5fccaea48c9e9e46f990...c771a70e6277c0a99b617c7a806ffedaca235ff9">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/init` from 4.37.0 to 4.37.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/init's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.3</h2>
<p>No user facing changes.</p>
<h2>v4.37.2</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>v4.37.1</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/init's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>4.36.1 - 02 Jun 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.0 - 22 May 2026</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81"><code>e4fba86</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4031">#4031</a>
from github/update-v4.37.3-72f6a9da0</li>
<li><a
href="https://github.com/github/codeql-action/commit/fb50ab5d62a274adf3ef3e22cfe750ae87a0ede7"><code>fb50ab5</code></a>
Update changelog for v4.37.3</li>
<li><a
href="https://github.com/github/codeql-action/commit/72f6a9da0def52d9193d6a758f0378b65091f8d1"><code>72f6a9d</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4030">#4030</a>
from github/mbg/fix/no-proxy</li>
<li><a
href="https://github.com/github/codeql-action/commit/3b5ee58597653d9cc6785f3f1277f796d81f3646"><code>3b5ee58</code></a>
Use default <code>request</code> options instead of
<code>undefined</code></li>
<li><a
href="https://github.com/github/codeql-action/commit/bfb6be4b5ecd3650f02f530571453e8c64ef0778"><code>bfb6be4</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4028">#4028</a>
from github/mergeback/v4.37.2-to-main-e0647621</li>
<li><a
href="https://github.com/github/codeql-action/commit/526ab84f9858816d9cf5f7b9df4dd5e2235f0eba"><code>526ab84</code></a>
Rebuild</li>
<li><a
href="https://github.com/github/codeql-action/commit/d6217b9b8c14166e4851db94c11155d03bd13c07"><code>d6217b9</code></a>
Update changelog and version after v4.37.2</li>
<li><a
href="https://github.com/github/codeql-action/commit/e0647621c2984b5ed2f768cb892365bf2a616ad1"><code>e064762</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4027">#4027</a>
from github/update-v4.37.2-385bcdc5a</li>
<li><a
href="https://github.com/github/codeql-action/commit/e0faed839190caa67a5cd42f1cc16246028ca3df"><code>e0faed8</code></a>
Add a couple of change notes</li>
<li><a
href="https://github.com/github/codeql-action/commit/73aad0eaa9df172668665a150d17b8bc5a650c20"><code>73aad0e</code></a>
Update changelog for v4.37.2</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/99df26d4f13ea111d4ec1a7dddef6063f76b97e9...e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/autobuild` from 4.37.0 to 4.37.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/autobuild's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.3</h2>
<p>No user facing changes.</p>
<h2>v4.37.2</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>v4.37.1</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/autobuild's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>4.36.1 - 02 Jun 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.0 - 22 May 2026</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81"><code>e4fba86</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4031">#4031</a>
from github/update-v4.37.3-72f6a9da0</li>
<li><a
href="https://github.com/github/codeql-action/commit/fb50ab5d62a274adf3ef3e22cfe750ae87a0ede7"><code>fb50ab5</code></a>
Update changelog for v4.37.3</li>
<li><a
href="https://github.com/github/codeql-action/commit/72f6a9da0def52d9193d6a758f0378b65091f8d1"><code>72f6a9d</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4030">#4030</a>
from github/mbg/fix/no-proxy</li>
<li><a
href="https://github.com/github/codeql-action/commit/3b5ee58597653d9cc6785f3f1277f796d81f3646"><code>3b5ee58</code></a>
Use default <code>request</code> options instead of
<code>undefined</code></li>
<li><a
href="https://github.com/github/codeql-action/commit/bfb6be4b5ecd3650f02f530571453e8c64ef0778"><code>bfb6be4</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4028">#4028</a>
from github/mergeback/v4.37.2-to-main-e0647621</li>
<li><a
href="https://github.com/github/codeql-action/commit/526ab84f9858816d9cf5f7b9df4dd5e2235f0eba"><code>526ab84</code></a>
Rebuild</li>
<li><a
href="https://github.com/github/codeql-action/commit/d6217b9b8c14166e4851db94c11155d03bd13c07"><code>d6217b9</code></a>
Update changelog and version after v4.37.2</li>
<li><a
href="https://github.com/github/codeql-action/commit/e0647621c2984b5ed2f768cb892365bf2a616ad1"><code>e064762</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4027">#4027</a>
from github/update-v4.37.2-385bcdc5a</li>
<li><a
href="https://github.com/github/codeql-action/commit/e0faed839190caa67a5cd42f1cc16246028ca3df"><code>e0faed8</code></a>
Add a couple of change notes</li>
<li><a
href="https://github.com/github/codeql-action/commit/73aad0eaa9df172668665a150d17b8bc5a650c20"><code>73aad0e</code></a>
Update changelog for v4.37.2</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/99df26d4f13ea111d4ec1a7dddef6063f76b97e9...e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/analyze` from 4.37.0 to 4.37.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/analyze's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.3</h2>
<p>No user facing changes.</p>
<h2>v4.37.2</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>v4.37.1</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/analyze's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>4.36.1 - 02 Jun 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.0 - 22 May 2026</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81"><code>e4fba86</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4031">#4031</a>
from github/update-v4.37.3-72f6a9da0</li>
<li><a
href="https://github.com/github/codeql-action/commit/fb50ab5d62a274adf3ef3e22cfe750ae87a0ede7"><code>fb50ab5</code></a>
Update changelog for v4.37.3</li>
<li><a
href="https://github.com/github/codeql-action/commit/72f6a9da0def52d9193d6a758f0378b65091f8d1"><code>72f6a9d</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4030">#4030</a>
from github/mbg/fix/no-proxy</li>
<li><a
href="https://github.com/github/codeql-action/commit/3b5ee58597653d9cc6785f3f1277f796d81f3646"><code>3b5ee58</code></a>
Use default <code>request</code> options instead of
<code>undefined</code></li>
<li><a
href="https://github.com/github/codeql-action/commit/bfb6be4b5ecd3650f02f530571453e8c64ef0778"><code>bfb6be4</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4028">#4028</a>
from github/mergeback/v4.37.2-to-main-e0647621</li>
<li><a
href="https://github.com/github/codeql-action/commit/526ab84f9858816d9cf5f7b9df4dd5e2235f0eba"><code>526ab84</code></a>
Rebuild</li>
<li><a
href="https://github.com/github/codeql-action/commit/d6217b9b8c14166e4851db94c11155d03bd13c07"><code>d6217b9</code></a>
Update changelog and version after v4.37.2</li>
<li><a
href="https://github.com/github/codeql-action/commit/e0647621c2984b5ed2f768cb892365bf2a616ad1"><code>e064762</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4027">#4027</a>
from github/update-v4.37.2-385bcdc5a</li>
<li><a
href="https://github.com/github/codeql-action/commit/e0faed839190caa67a5cd42f1cc16246028ca3df"><code>e0faed8</code></a>
Add a couple of change notes</li>
<li><a
href="https://github.com/github/codeql-action/commit/73aad0eaa9df172668665a150d17b8bc5a650c20"><code>73aad0e</code></a>
Update changelog for v4.37.2</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/99df26d4f13ea111d4ec1a7dddef6063f76b97e9...e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-05 09:23:34 -04:00
dependabot[bot] 42f308bc89 Bump the npm-deps group in /src/frontend with 7 updates (#1155)
Bumps the npm-deps group in /src/frontend with 7 updates:

| Package | From | To |
| --- | --- | --- |
| [react](https://github.com/react/react/tree/HEAD/packages/react) |
`19.2.7` | `19.2.8` |
|
[react-dom](https://github.com/react/react/tree/HEAD/packages/react-dom)
| `19.2.7` | `19.2.8` |
|
[@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)
| `26.1.1` | `26.1.2` |
|
[@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react)
| `6.0.3` | `6.0.4` |
| [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) |
`6.27.0` | `6.29.0` |
| [oxfmt](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt) |
`0.59.0` | `0.61.0` |
| [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) |
`1.74.0` | `1.76.0` |

Updates `react` from 19.2.7 to 19.2.8
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/react/react/releases">react's
releases</a>.</em></p>
<blockquote>
<h2>19.2.8 (July 21st, 2026)</h2>
<h2>React Server Components</h2>
<ul>
<li>Performance improvements when decoding
(<a
href="https://redirect.github.com/facebook/react/pull/37087">#37087</a>
by <a href="https://github.com/eps1lon"><code>@​eps1lon</code></a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/react/react/commit/1dd4ecbdabf826f527fc9a58c05ea70375b7d170"><code>1dd4ecb</code></a>
[FlightReply] Performance improvements when decoding (<a
href="https://github.com/react/react/tree/HEAD/packages/react/issues/37087">#37087</a>)</li>
<li><a
href="https://github.com/react/react/commit/b0d2fdb78bdfae075a7fa02ddcebbf25f90952c2"><code>b0d2fdb</code></a>
[19.2.x] Update required references to GitHub repo (<a
href="https://github.com/react/react/tree/HEAD/packages/react/issues/36753">#36753</a>)</li>
<li>See full diff in <a
href="https://github.com/react/react/commits/v19.2.8/packages/react">compare
view</a></li>
</ul>
</details>
<br />

Updates `react-dom` from 19.2.7 to 19.2.8
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/react/react/releases">react-dom's
releases</a>.</em></p>
<blockquote>
<h2>19.2.8 (July 21st, 2026)</h2>
<h2>React Server Components</h2>
<ul>
<li>Performance improvements when decoding
(<a
href="https://redirect.github.com/facebook/react/pull/37087">#37087</a>
by <a href="https://github.com/eps1lon"><code>@​eps1lon</code></a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/react/react/commit/1dd4ecbdabf826f527fc9a58c05ea70375b7d170"><code>1dd4ecb</code></a>
[FlightReply] Performance improvements when decoding (<a
href="https://github.com/react/react/tree/HEAD/packages/react-dom/issues/37087">#37087</a>)</li>
<li><a
href="https://github.com/react/react/commit/b0d2fdb78bdfae075a7fa02ddcebbf25f90952c2"><code>b0d2fdb</code></a>
[19.2.x] Update required references to GitHub repo (<a
href="https://github.com/react/react/tree/HEAD/packages/react-dom/issues/36753">#36753</a>)</li>
<li>See full diff in <a
href="https://github.com/react/react/commits/v19.2.8/packages/react-dom">compare
view</a></li>
</ul>
</details>
<br />

Updates `@types/node` from 26.1.1 to 26.1.2
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node">compare
view</a></li>
</ul>
</details>
<br />

Updates `@vitejs/plugin-react` from 6.0.3 to 6.0.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite-plugin-react/releases">@​vitejs/plugin-react's
releases</a>.</em></p>
<blockquote>
<h2>plugin-react@6.0.4</h2>
<h3>Fixed <code>$RefreshSig$ is not defined</code> error when running
<code>vite dev</code> with <code>NODE_ENV=production</code></h3>
<p>When running <code>vite dev</code> with
<code>NODE_ENV=production</code>, the app errored with
<code>$RefreshSig$ is not defined</code>.
This error is now fixed.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md">@​vitejs/plugin-react's
changelog</a>.</em></p>
<blockquote>
<h2>6.0.4 (2026-07-22)</h2>
<h3>Fixed <code>$RefreshSig$ is not defined</code> error when running
<code>vite dev</code> with <code>NODE_ENV=production</code></h3>
<p>When running <code>vite dev</code> with
<code>NODE_ENV=production</code>, the app errored with
<code>$RefreshSig$ is not defined</code>.
This error is now fixed.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/f4b549822ec239799d746c030abb0b9a7d8f0a04"><code>f4b5498</code></a>
release: plugin-react@6.0.4</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/7a4065906e07e616aee23c4fcc76a76b74a7d694"><code>7a40659</code></a>
fix(react): <code>$RefreshSig$ is not defined</code> with
NODE_ENV=production vite dev ...</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/98b32d47a3f2517d52e8a51d8e28ff2ed13678a2"><code>98b32d4</code></a>
fix(deps): update react 19.2.8 (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1298">#1298</a>)</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/8ae5449be23079dd17fdefc64064a3d94be6fc39"><code>8ae5449</code></a>
fix: babel-plugin-react-compiler cannot be imported when used in a
framework ...</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/f09ea01b0b66a4900c3e42cef2aa0147e78b097c"><code>f09ea01</code></a>
fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1282">#1282</a>)</li>
<li>See full diff in <a
href="https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.0.4/packages/plugin-react">compare
view</a></li>
</ul>
</details>
<br />

Updates `knip` from 6.27.0 to 6.29.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/webpro-nl/knip/releases">knip's
releases</a>.</em></p>
<blockquote>
<h2>Release 6.29.0</h2>
<ul>
<li>fix(glob): do not follow symbolic links in async glob (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1900">#1900</a>)
(b7d5ce2f545d91953a97733aa1567e8728b20644) - thanks <a
href="https://github.com/mogelbrod"><code>@​mogelbrod</code></a>!</li>
<li>Add built-in compiler for tsrx
(db05e0142a41fad2ad45d62ba5106e0d97c92b45)</li>
<li>Fix -u shortcut for --duration
(932b9262af275d209923a5efab1b6f23067bb006)</li>
</ul>
<h2>Release 6.28.0</h2>
<ul>
<li>Update oxc-resolver (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1889">#1889</a>)
(1444f6aaf04d42ad07ee0b88da4dfae0e116043a)</li>
<li>Keep entry-exported members public (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1890">#1890</a>)
(94967c13a589a7e83408b1495c31323ac126fcab)</li>
<li>Don't un-ignore sibling paths on unrelated gitignore negations
(resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1891">#1891</a>)
(5c026ba50c1e89804fc5094a52dc4c4c37d92bc2)</li>
<li>Memoize the gitignore shadowed-pattern check
(a5fa3b0ebb7f84c107dba1c1ca9a9b73995f668f)</li>
<li>Ignore nested files under a gitignore dir/* pattern
(404a53e3e49fa7c1f51b60bcb30ea50450f75aab)</li>
<li>Track types referenced by JSDoc imports (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1888">#1888</a>)
(6ae48aadbe03952690c8608e6df42bd039cf6624)</li>
<li>Update oxc-parser (02ca17a72c3f0afa7df32297d2dd2f5463ac6e00)</li>
<li>feat: markdownlint-cli2 (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1887">#1887</a>)
(323b96f55b38b53f5e4648d72fecd968fae35147) - thanks <a
href="https://github.com/Zamiell"><code>@​Zamiell</code></a>!</li>
<li>Update rolldown snapshot
(009e509dee6a5013fd3937abb4ec81684601b023)</li>
<li>Resolve $GITHUB_ACTION_PATH in GHA composite actions
(ead489f066733f701acfe5b81eac0a6c813fb5c5)</li>
<li>Don't report <code>node:</code> specifiers as unlisted dependencies
(1a7114f0a159fe82328041e11fc61c5c1730cc01)</li>
<li>Discover tsdown CLI entries
(0d47a45bfc518a7bf73a4b4878aeab236d6f9791)</li>
<li>Discover Convex function modules
(e01f21c44773186e7ad74e7badf9fc81b82edfb5)</li>
<li>Ignore common system binaries
(cd003065fda5422550a412bba07ebbb1f9bae126)</li>
<li>Add OpenClaw plugin (73a2d7dd45f65ea312fde38cb4fa4f5a4fc34bf2)</li>
<li>Discover Tailwind CLI input stylesheet (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1551">#1551</a>)
(9ae5feed922ecb7e8c436545e9b45125bd73b457)</li>
<li>Discover Angular global stylesheets from angular.json
(fb9e0488f6f21284714d0651fcf1dd2de02d425a)</li>
<li>Hint when project patterns exclude a compiled extension
(74de516d3d73839536fc2a43c2c0ec38a6299631)</li>
<li>Ignore common global binaries
(a99a206015fe414a4de12016cc62499910afaf6f)</li>
<li>Run multiple preprocessors in sequence
(844358367e9d1939bbf47f8dcb97d810741f56c1)</li>
<li>fix(nuxt): resolve nuxt module paths (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1896">#1896</a>)
(3c8594f5ffc641b00575b6d7455990db1277ac75) - thanks <a
href="https://github.com/danielroe"><code>@​danielroe</code></a>!</li>
<li>Report unused re-exports when ignoreExportsUsedInFile is set (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1895">#1895</a>)
(a113a5f4eedc81ef56b00a3e488cc94febefede5) - thanks <a
href="https://github.com/mlm20"><code>@​mlm20</code></a>!</li>
<li>Pass -w to pnpm add in sentry ecosystem test
(ea8c8ac6fe9519f00ca6a61131b345f5de521704)</li>
<li>Update sentry snapshot
(b75768d544d9f869ebce8f8c6dd8490f3db87023)</li>
<li>Record all traversed dirs in glob cache (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1897">#1897</a>)
(81be1d436eae15518df764cbcb443899e11a9569)</li>
<li>Invalidate glob cache when .gitignore changes
(baff301f2dceda97bf8bf73809d811cc44c72c8c)</li>
<li>Improve Nuxt and Nitro plugin resolution
(86ffab7bcdd095631d6b0b17254e8acd6b5022bc)</li>
<li>fix: don't treat [-]-c[heck] as config flag for prettier (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1902">#1902</a>)
(b7266ee8240a9b8686e91a44a4706d3605d094c4) - thanks <a
href="https://github.com/davidkna-sap"><code>@​davidkna-sap</code></a>!</li>
<li>Use Entra ID to publish VS Code extension
(4236253482f884d92cfde2ccf85c7fb9aca17f7e)</li>
<li>Remove obsolete trust policy exclusions
(630d5f9835f4303ec9aa7d090ad40a177808c4fb)</li>
<li>Update dependencies (810147b5aa21bb57ad103fd19ba92844e42cd84c)</li>
<li>Migrate from typescript/native-preview to typescript
(140a25a577a64273a7a77f2ff2c8f224118ac298)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/webpro-nl/knip/commit/d9a6fe096fe2711a54c62dbbe988fa9221267538"><code>d9a6fe0</code></a>
Release knip@6.29.0</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/932b9262af275d209923a5efab1b6f23067bb006"><code>932b926</code></a>
Fix -u shortcut for --duration</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/db05e0142a41fad2ad45d62ba5106e0d97c92b45"><code>db05e01</code></a>
Add built-in compiler for tsrx</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/b7d5ce2f545d91953a97733aa1567e8728b20644"><code>b7d5ce2</code></a>
fix(glob): do not follow symbolic links in async glob (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1900">#1900</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/fc16b3875eabd27315c46653056a4a5b9eca4366"><code>fc16b38</code></a>
Release knip@6.28.0</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/140a25a577a64273a7a77f2ff2c8f224118ac298"><code>140a25a</code></a>
Migrate from typescript/native-preview to typescript</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/810147b5aa21bb57ad103fd19ba92844e42cd84c"><code>810147b</code></a>
Update dependencies</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/b7266ee8240a9b8686e91a44a4706d3605d094c4"><code>b7266ee</code></a>
fix: don't treat [-]-c[heck] as config flag for prettier (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1902">#1902</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/86ffab7bcdd095631d6b0b17254e8acd6b5022bc"><code>86ffab7</code></a>
Improve Nuxt and Nitro plugin resolution</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/baff301f2dceda97bf8bf73809d811cc44c72c8c"><code>baff301</code></a>
Invalidate glob cache when .gitignore changes</li>
<li>Additional commits viewable in <a
href="https://github.com/webpro-nl/knip/commits/knip@6.29.0/packages/knip">compare
view</a></li>
</ul>
</details>
<br />

Updates `oxfmt` from 0.59.0 to 0.61.0
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/blob/main/npm/oxfmt/CHANGELOG.md">oxfmt's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<p>All notable changes to this package will be documented in this
file.</p>
<p>The format is based on <a
href="https://keepachangelog.com/en/1.0.0">Keep a Changelog</a>.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/commit/65fe65d8429e1d1bdf86c517ff08bd119ee87660"><code>65fe65d</code></a>
release(apps): oxlint v1.76.0 &amp;&amp; oxfmt v0.61.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/24976">#24976</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/83abe3b49c0913b1a984a7eec5e433a59fd76eae"><code>83abe3b</code></a>
release(apps): oxlint v1.75.0 &amp;&amp; oxfmt v0.60.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/24713">#24713</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/e2e0f94253c5ce91a00394e2cba974bc152798db"><code>e2e0f94</code></a>
chore(docs): update sponsor section (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/24588">#24588</a>)</li>
<li>See full diff in <a
href="https://github.com/oxc-project/oxc/commits/oxfmt_v0.61.0/npm/oxfmt">compare
view</a></li>
</ul>
</details>
<br />

Updates `oxlint` from 1.74.0 to 1.76.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/releases">oxlint's
releases</a>.</em></p>
<blockquote>
<h2>oxlint v1.27.0 &amp;&amp; oxfmt v0.12.0</h2>
<h1>Oxlint v1.27.0</h1>
<h3>🚀 Features</h3>
<ul>
<li>222a8f0 linter/plugins: Implement
<code>SourceCode#isSpaceBetween</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15498">#15498</a>)
(overlookmotel)</li>
<li>2f9735d linter/plugins: Implement
<code>context.languageOptions</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15486">#15486</a>)
(overlookmotel)</li>
<li>bc731ff linter/plugins: Stub out all <code>Context</code> APIs (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15479">#15479</a>)
(overlookmotel)</li>
<li>5822cb4 linter/plugins: Add <code>extend</code> method to
<code>FILE_CONTEXT</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15477">#15477</a>)
(overlookmotel)</li>
<li>7b1e6f3 apps: Add pure rust binaries and release to github (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15469">#15469</a>)
(Boshen)</li>
<li>2a89b43 linter: Introduce debug assertions after fixes to assert
validity (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15389">#15389</a>)
(camc314)</li>
<li>ad3c45a editor: Add <code>oxc.path.node</code> option (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15040">#15040</a>)
(Sysix)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>6f3cd77 linter/no-var: Incorrect warning for blocks (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15504">#15504</a>)
(Hamir Mahal)</li>
<li>6957fb9 linter/plugins: Do not allow access to
<code>Context#id</code> in <code>createOnce</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15489">#15489</a>)
(overlookmotel)</li>
<li>7409630 linter/plugins: Allow access to <code>cwd</code> in
<code>createOnce</code> in ESLint interop mode (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15488">#15488</a>)
(overlookmotel)</li>
<li>732205e parser: Reject <code>using</code> / <code>await using</code>
in a switch <code>case</code> / <code>default</code> clause (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15225">#15225</a>)
(sapphi-red)</li>
<li>a17ca32 linter/plugins: Replace <code>Context</code> class (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15448">#15448</a>)
(overlookmotel)</li>
<li>ecf2f7b language_server: Fail gracefully when tsgolint executable
not found (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15436">#15436</a>)
(camc314)</li>
<li>3c8d3a7 lang-server: Improve logging in failure case for tsgolint
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15299">#15299</a>)
(camc314)</li>
<li>ef71410 linter: Use jsx if source type is JS in fix debug assertion
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15434">#15434</a>)
(camc314)</li>
<li>e32bbf6 linter/no-var: Handle TypeScript declare keyword in fixer
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15426">#15426</a>)
(camc314)</li>
<li>6565dbe linter/switch-case-braces: Skip comments when searching for
<code>:</code> token (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15425">#15425</a>)
(camc314)</li>
<li>85bd19a linter/prefer-class-fields: Insert value after type
annotation in fixer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15423">#15423</a>)
(camc314)</li>
<li>fde753e linter/plugins: Block access to
<code>context.settings</code> in <code>createOnce</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15394">#15394</a>)
(overlookmotel)</li>
<li>ddd9f9f linter/forward-ref-uses-ref: Dont suggest removing wrapper
in invalid positions (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15388">#15388</a>)
(camc314)</li>
<li>dac2a9c linter/no-template-curly-in-string: Remove fixer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15387">#15387</a>)
(camc314)</li>
<li>989b8e3 linter/no-var: Only fix to <code>const</code> if the var has
an initializer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15385">#15385</a>)
(camc314)</li>
<li>cc403f5 linter/plugins: Return empty object for unimplemented
parserServices (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15364">#15364</a>)
(magic-akari)</li>
</ul>
<h3>⚡ Performance</h3>
<ul>
<li>25d577e language_server: Start tools in parallel (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15500">#15500</a>)
(Sysix)</li>
<li>3c57291 linter/plugins: Optimize loops (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15449">#15449</a>)
(overlookmotel)</li>
<li>3166233 linter/plugins: Remove <code>Arc</code>s (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15431">#15431</a>)
(overlookmotel)</li>
<li>9de1322 linter/plugins: Lazily deserialize settings JSON (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15395">#15395</a>)
(overlookmotel)</li>
<li>3049ec2 linter/plugins: Optimize <code>deepFreezeSettings</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15392">#15392</a>)
(overlookmotel)</li>
<li>444ebfd linter/plugins: Use single object for
<code>parserServices</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15378">#15378</a>)
(overlookmotel)</li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>97d2104 linter: Update comment in lint.rs about default value for
tsconfig path (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15530">#15530</a>)
(Connor Shea)</li>
<li>2c6bd9e linter: Always refer as &quot;ES2015&quot; instead of
&quot;ES6&quot; (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15411">#15411</a>)
(sapphi-red)</li>
<li>a0c5203 linter/import/named: Update &quot;ES7&quot; comment in
examples (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15410">#15410</a>)
(sapphi-red)</li>
<li>3dc24b5 linter,minifier: Always refer as &quot;ES Modules&quot;
instead of &quot;ES6 Modules&quot; (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15409">#15409</a>)
(sapphi-red)</li>
<li>2ad77fb linter/no-this-before-super: Correct &quot;Why is this
bad?&quot; section (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15408">#15408</a>)
(sapphi-red)</li>
<li>57f0ce1 linter: Add backquotes where appropriate (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15407">#15407</a>)
(sapphi-red)</li>
</ul>
<h1>Oxfmt v0.12.0</h1>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md">oxlint's
changelog</a>.</em></p>
<blockquote>
<h2>[1.76.0] - 2026-07-27</h2>
<h3>🚀 Features</h3>
<ul>
<li>8d31dfa linter: Verify eslint/no-restricted-globals config schema
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24598">#24598</a>)
(vigneshwar)</li>
<li>7069621 linter: Verify jest/vitest prefer-lowercase-title config
schema (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24724">#24724</a>)
(Bartok)</li>
<li>016cf2a linter/oxc: Add bad-match-all-arg rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24900">#24900</a>)
(camc314)</li>
<li>cdc941e linter/n: Implement <code>exports-style</code> rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24087">#24087</a>)
(Mikhail Baev)</li>
<li>1ad6f6c linter/eslint: Implement <code>id-denylist</code> rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24632">#24632</a>)
(Mikhail Baev)</li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>3ff2e0e linter: Clarify config extends types (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24936">#24936</a>)
(Boshen)</li>
</ul>
<h2>[1.75.0] - 2026-07-20</h2>
<h3>🚀 Features</h3>
<ul>
<li>dd18383 linter/node: Implement no-top-level-await rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24634">#24634</a>)
(Connor Shea)</li>
<li>16a65f2 linter/react: Implement function-component-definition rule
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24471">#24471</a>)
(Cole Ellison)</li>
<li>7f1f585 linter: Reuse <code>jest/padding-around-test-blocks</code>
for <code>vitest/padding-around-test-blocks</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24519">#24519</a>)
(Mikhail Baev)</li>
<li>99978a8 linter/import/consistent-type-specifier-style: Support
<code>prefer-top-level-if-only-type-imports</code> option (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24502">#24502</a>)
(camc314)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>8694167 linter/eslint/prefer-destructuring: Handle typed
declarations (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24616">#24616</a>)
(camc314)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/commit/65fe65d8429e1d1bdf86c517ff08bd119ee87660"><code>65fe65d</code></a>
release(apps): oxlint v1.76.0 &amp;&amp; oxfmt v0.61.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24976">#24976</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/3ff2e0e6fa1670d2300b2fb44dddc08c2df50a00"><code>3ff2e0e</code></a>
docs(linter): clarify config extends types (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24936">#24936</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/8d31dfa3934b0c8d276e022ad546836ee6d1dcd7"><code>8d31dfa</code></a>
feat(linter): verify eslint/no-restricted-globals config schema (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24598">#24598</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/7069621199a47158b853743ab6664126ec7b24b4"><code>7069621</code></a>
feat(linter): verify jest/vitest prefer-lowercase-title config schema
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24724">#24724</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/016cf2acd64e4f6d34226aecc84e69d342708858"><code>016cf2a</code></a>
feat(linter/oxc): add bad-match-all-arg rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24900">#24900</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/cdc941e5f81d00dfca63cc17cc39eb6d1462a9ed"><code>cdc941e</code></a>
feat(linter/n): implement <code>exports-style</code> rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24087">#24087</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/1ad6f6c823da92a9b70a9f8c23d355d1b99f9e75"><code>1ad6f6c</code></a>
feat(linter/eslint): implement <code>id-denylist</code> rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24632">#24632</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/83abe3b49c0913b1a984a7eec5e433a59fd76eae"><code>83abe3b</code></a>
release(apps): oxlint v1.75.0 &amp;&amp; oxfmt v0.60.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24713">#24713</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/6ce51d3ff39d675bb3ce12abe31c5807f9683833"><code>6ce51d3</code></a>
chore(deps): update dependency oxlint-tsgolint to v7.0.2000 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24763">#24763</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/dd18383ee1504b319aee45f832045bcd1a7285d5"><code>dd18383</code></a>
feat(linter/node): implement no-top-level-await rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24634">#24634</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/oxc-project/oxc/commits/oxlint_v1.76.0/npm/oxlint">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-05 09:23:22 -04:00
CamGoed e93fbd2a9b Fix issue with stale v1 hash queries to qbittorrent (#1162)
## Problem

Shelfmark can lose track of hybrid v1/v2 torrents after qBittorrent
completes their metadata download.

Shelfmark initially identifies the torrent by its v1 infohash. Once
metadata resolves, qBittorrent may switch the torrent’s primary `hash`
to the truncated v2 hash, causing lookups using the original v1 hash to
return nothing.

For example:

- v1: `edf46c7f938a3c678081734d7bff8b9c652ba5e5`
- qBittorrent `hash`: `0bed5f40753b342cb143e83c2b21924cc8474731`
- full v2:
`0bed5f40753b342cb143e83c2b21924cc847473134e44d1bd300bdc58c13010f`

At that point, querying `/api/v2/torrents/info` with the original v1
hash returns no records. Querying with the new primary hash works, and
the returned record still contains the original hash in `infohash_v1`.

`find_existing()` also ignored its provided category, so audiobook
torrents fall back to the default ebook category instead. This means the
fallback method for a mismatched download ID never occurs for
audiobooks, leading to a "failed" download that is actually successful
in qBittorrent. As a result, the downloaded files are not automatically
transferred/hardlinked to the output directory.

## Fix

- Match torrents against `hash`, `infohash_v1`, and `infohash_v2`.
- Wait for magnet metadata to finish downloading before returning the
torrent ID.
- Return qBittorrent’s current primary `hash`.
- Search the provided category first, then the configured default, and
finally the full torrent list.


Logs showing the issue:

```
2026-08-03 22:42:30,262 - shelfmark.release_sources.audiobookbay.scraper - DEBUG - scraper.py:450 - Generated Magnet Link: magnet:?xt=urn:btih:EDF46C7F938A3C678081734D7BFF8B9C652BA5E5&tr=...

2026-08-03 22:42:30,376 - shelfmark.download.clients.qbittorrent - DEBUG - qbittorrent.py:521 - qBittorrent add result: TorrentsAddedMetadata({'added_torrent_ids': ['edf46c7f938a3c678081734d7bff8b9c652ba5e5'], 'failure_count': 0, 'pending_count': 0, 'success_count': 1})

2026-08-03 22:42:30,427 - shelfmark.download.clients.qbittorrent - INFO - qbittorrent.py:545 - Added torrent: edf46c7f938a3c678081734d7bff8b9c652ba5e5

2026-08-03 22:42:30,427 - shelfmark.download.clients.base_handler - INFO - base_handler.py:868 - Added to qbittorrent: edf46c7f938a3c678081734d7bff8b9c652ba5e5 for 'Pathogenesis: A History of the World in Eight Plagues'

2026-08-03 22:42:30,427 - shelfmark.download.clients.base_handler - DEBUG - base_handler.py:906 - Starting poll for edf46c7f938a3c678081734d7bff8b9c652ba5e5 (content_type=audiobook)

2026-08-03 22:42:32,590 - shelfmark.download.clients.base_handler - DEBUG - base_handler.py:958 - Download edf46c7f938a3c678081734d7bff8b9c652ba5e5 not yet visible in client (attempt 1/15)

2026-08-03 22:43:02,345 - shelfmark.download.clients.base_handler - ERROR - base_handler.py:969 - Download edf46c7f938a3c678081734d7bff8b9c652ba5e5 not found after 15 attempts

2026-08-03 22:43:02,345 - shelfmark.download.clients.base_handler - INFO - base_handler.py:426 - Skipping download client cleanup for protocol=torrent after download error (client=qbittorrent id=edf46c7f938a3c678081734d7bff8b9c652ba5e5)
```
<br>


The successful torrent: 
<br>


<img width="968" height="159" alt="image"
src="https://github.com/user-attachments/assets/d63c4444-6a4d-4214-97a0-732acc338970"
/>
<br>


v1 vs v2 hash: 
<br>


<img width="749" height="212" alt="image"
src="https://github.com/user-attachments/assets/d985b78a-84fe-4a26-9697-126c787e7303"
/>


I ran some python queries from the shelfmark container that show the
mismatch:

```
qBittorrent URL: http://gluetun-mam:8081
Tracked hash:    edf46c7f938a3c678081734d7bff8b9c652ba5e5

=== PROPERTIES LOOKUP USING SHELFMARK HASH ===
HTTP status: 404
Not Found

=== EXACT /torrents/info HASH LOOKUP ===
Returned torrents: 0
=== FIND VISIBLE PATHOGENESIS TORRENT ===
Matching visible torrents: 1

Name:         Pathogenesis: A History of the World in Eight Plagues
Primary hash: 0bed5f40753b342cb143e83c2b21924cc8474731
Category:     audiobooks
State:        stalledUP
Progress:     1
Properties HTTP status: 200
Infohash v1:  edf46c7f938a3c678081734d7bff8b9c652ba5e5
Infohash v2:  0bed5f40753b342cb143e83c2b21924cc847473134e44d1bd300bdc58c13010f
```
2026-08-05 09:19:25 -04:00
dependabot[bot]andCaliBrain cdd156e142 Bump the python-deps group across 1 directory with 2 updates (#1153)
Bumps the python-deps group with 2 updates in the / directory:
[tqdm](https://github.com/tqdm/tqdm) and
[seleniumbase](https://github.com/seleniumbase/SeleniumBase).

Updates `tqdm` from 4.69.1 to 4.70.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tqdm/tqdm/releases">tqdm's
releases</a>.</em></p>
<blockquote>
<h2>tqdm v4.70.0 stable</h2>
<ul>
<li><code>contrib.concurrent</code>: major improvements
<ul>
<li>support <code>process_map(mp_context, max_tasks_per_child)</code>,
<code>thread_map(thread_name_prefix)</code> (<a
href="https://redirect.github.com/tqdm/tqdm/issues/1265">#1265</a>)</li>
<li>fix <code>total</code> based on shortest iterable length (<a
href="https://redirect.github.com/tqdm/tqdm/issues/1473">#1473</a>)</li>
<li>use default <code>max_workers</code> (<a
href="https://redirect.github.com/tqdm/tqdm/issues/1543">#1543</a> &lt;-
<a href="https://redirect.github.com/tqdm/tqdm/issues/1530">#1530</a>,
<a
href="https://redirect.github.com/tqdm/tqdm/issues/1518">#1518</a>)</li>
<li>support <code>timeout</code>, <code>buffersize</code> (<a
href="https://redirect.github.com/tqdm/tqdm/issues/1576">#1576</a>)</li>
<li>improve ETA (<a
href="https://redirect.github.com/tqdm/tqdm/issues/1708">#1708</a> &lt;-
<a
href="https://redirect.github.com/tqdm/tqdm/issues/1161">#1161</a>)</li>
<li>update <code>as_completed</code> (<a
href="https://redirect.github.com/tqdm/tqdm/issues/1709">#1709</a> &lt;-
<a
href="https://redirect.github.com/tqdm/tqdm/issues/1565">#1565</a>)</li>
<li>add <code>tqdm.concurrent.intepreter_map</code> (<a
href="https://redirect.github.com/tqdm/tqdm/issues/1777">#1777</a>)</li>
</ul>
</li>
<li><code>asyncio</code>: support iterables with only
<code>__aiter__</code> (<a
href="https://redirect.github.com/tqdm/tqdm/issues/1714">#1714</a> &lt;-
<a
href="https://redirect.github.com/tqdm/tqdm/issues/1686">#1686</a>)</li>
<li>support <code>reset(float(&quot;inf&quot;))</code> (<a
href="https://redirect.github.com/tqdm/tqdm/issues/1783">#1783</a> &lt;-
<a href="https://redirect.github.com/tqdm/tqdm/issues/1781">#1781</a>,
<a
href="https://redirect.github.com/tqdm/tqdm/issues/651">#651</a>)</li>
<li>framework: test &amp; reduce wheel size (<a
href="https://redirect.github.com/tqdm/tqdm/issues/1782">#1782</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/tqdm/tqdm/commit/96f2e60e4584cdab57a23602e27043d0465254ad"><code>96f2e60</code></a>
Merge pull request <a
href="https://redirect.github.com/tqdm/tqdm/issues/1777">#1777</a> from
shermansiu/feat/interpreter-pool</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/c27393ec3a0a91d83dd53f04b80ee61f9c0cd5e4"><code>c27393e</code></a>
misc tidy</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/061c6236d56f8c1ed0934606c3b4ee5c59cf122e"><code>061c623</code></a>
Disable <code>tqdm.monitor_interval</code> for subinterpreters because
they do not have ...</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/9fc160ba7f959f955058985b0d1122fc9f0b2729"><code>9fc160b</code></a>
Update how we check for <code>interpreter_map</code> support in the
tests</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/b42463a20fffc17ad4df2aa145076afb0dc4486d"><code>b42463a</code></a>
Ensure that subinterpreters can import tqdm while unpickling the
initializer</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/16d5486c402a2808ec9c398538e0bc56d00866de"><code>16d5486</code></a>
Add support for nested progress bars</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/9f5890fda6cf43e66782fbf34647f808bdc66d58"><code>9f5890f</code></a>
Add initial implementation for interpreter_map</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/321f920bb7368e27eee04fed1c0de2a8a236fd9d"><code>321f920</code></a>
Merge pull request <a
href="https://redirect.github.com/tqdm/tqdm/issues/1783">#1783</a> from
LuShadowX/reset-inf-total</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/4664b5701ebc5299290a1fb31a229fb1bfc64703"><code>4664b57</code></a>
minor tidy</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/426a0988a1e9970041f5a649ae8a6376c5293fbe"><code>426a098</code></a>
Treat inf total as unknown in reset() too</li>
<li>Additional commits viewable in <a
href="https://github.com/tqdm/tqdm/compare/v4.69.1...v4.70.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `seleniumbase` from 4.51.7 to 4.51.8
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.51.8 - Add <code>drag_and_drop()</code> with CDP</h2>
<h2>Add <code>drag_and_drop()</code> with CDP</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/b05c11f370e69d7566d59d90883c8e8fec1c0024">Add
a CDP-based drag_and_drop() method</a>
--&gt; This resolves <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4442">seleniumbase/SeleniumBase#4442</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/9d9ffb4f3b335da6674917b395958c9378186067">Update
CDP Mode examples</a>
--&gt; Specifically for <code>drag_and_drop()</code></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/57f2a4ec5dd286222b114acb5d2272ad459d787d">Update
the docs</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>Add <code>drag_and_drop()</code> with CDP by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4443">seleniumbase/SeleniumBase#4443</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.51.7...v4.51.8">https://github.com/seleniumbase/SeleniumBase/compare/v4.51.7...v4.51.8</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/8abc55e9f5ef3f2e1d8c4d92351bcbbf7163e7ce"><code>8abc55e</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4443">#4443</a>
from seleniumbase/drag-and-drop-with-cdp</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/89642ff4894e3351739dc359632566902379ca64"><code>89642ff</code></a>
Version 4.51.8</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/9d9ffb4f3b335da6674917b395958c9378186067"><code>9d9ffb4</code></a>
Update CDP Mode examples</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/57f2a4ec5dd286222b114acb5d2272ad459d787d"><code>57f2a4e</code></a>
Update the docs</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/b05c11f370e69d7566d59d90883c8e8fec1c0024"><code>b05c11f</code></a>
Add a CDP-based <code>drag_and_drop()</code> method</li>
<li>See full diff in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.51.7...v4.51.8">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: CaliBrain <calibrain@l4n.xyz>
2026-07-31 00:23:42 -04:00
CaliBrainandClaude Opus 5 21f2b6b95c Fix torrent post-import category follow-ups and clear the lint backlog (#1154)
rTorrent set_category and remove now uppercase the info hash, which
reaches us lowercase while rTorrent's XML-RPC lookups are case
sensitive. remove() had this bug before #1148, making
PROWLARR_TORRENT_ACTION=remove a silent no-op for rTorrent.

Transmission set_category appends the post-import label instead of
replacing the whole label list. The unsupported-client path in
post_process_cleanup logs at debug instead of warning, so Real-Debrid
and AllDebrid users stop seeing a warning on every successful import.

The Real-Debrid and AllDebrid clients now follow the conventions used by
the other clients (_raise_runtime_error helpers, narrow error tuples,
ClassVar, Path.open), and register_client is generic over a TypeVar
bound to DownloadClient so decorated classes keep their concrete type.
make fix and the lint, format and typecheck targets all pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 00:17:28 -04:00
CaliBrain 10554444a9 Create a Tor watchguard and make iptables non mandatory (#1152)
Fix https://github.com/calibrain/shelfmark/issues/1150
2026-07-30 23:41:55 -04:00
CaliBrain ce026e2eb8 IRC health checks (#1151) 2026-07-30 23:39:12 -04:00
Tilian B ff770940ca Add torrent post-import category action (#1148)
## Summary

- add a **Change Category** torrent completion action and conditionally
show its post-import category/label setting
- update kept torrents only after a successful library import
- support qBittorrent categories, Transmission labels, Deluge's Label
plugin, and rTorrent's `custom1` label
- preserve existing Keep/Remove behavior and document the new
environment setting

## Behavior

Category changes happen from `post_process_cleanup`, after output
transfer and post-processing complete. This keeps the existing hardlink
flow unchanged. An empty post-import category is a no-op, and client API
failures are logged without turning a successful library import into a
failure.

## Validation

- `pytest -n 0 tests/prowlarr/test_qbittorrent_client.py
tests/prowlarr/test_transmission_client.py
tests/prowlarr/test_deluge_client.py
tests/prowlarr/test_rtorrent_client.py tests/prowlarr/test_handler.py
tests/config/test_generate_env_docs.py` — 161 passed
- `ruff check` on all changed Python files — passed
- `basedpyright` on the changed client implementation files — passed
- full `basedpyright shelfmark/download/clients` currently reports two
pre-existing errors in the new debrid connection-test code at
`settings.py:546` and `settings.py:563`, outside this PR's diff
- multi-architecture Docker images built successfully for `linux/amd64`
and `linux/arm64`
2026-07-30 23:38:53 -04:00
PaulVumon 340853477f feat(download): add native Real-Debrid and AllDebrid debrid service clients (#1146)
## Summary
Adds native download client support for debrid services (**Real-Debrid**
and **AllDebrid**), routing magnet links and torrents through their APIs
to download content via their CDN infrastructure.

## Features
- **Real-Debrid Client (\RealDebridClient\)**:
- API v1.0 integration (magnet upload, file selection, status polling,
link unrestricting, HTTP download)
  - Premium account validation via connection test
- **AllDebrid Client (\AllDebridClient\)**:
- API v4/v4.1 integration (magnet upload, status polling, file tree
listing, link unlocking with delayed link support, HTTP download)
  - Multi-tier link resolution strategy with automatic fallback
  - Premium account validation via connection test
- **Common & UI**:
- Ebook & Audiobook file filtering (\.epub\, \.m4b\, \.mp3\, \.pdf\,
\.flac\, etc.)
- Settings UI options in \PROWLARR_TORRENT_CLIENT\ dropdown with API key
configuration and test connection buttons
2026-07-29 22:22:48 -04:00
816a735cde Add a {Language} naming template variable, and consolidate language resolution (#1142)
Fixes #1138
Fixes #1141

## Problem

Two language editions of one book resolve to the same canonical title,
so they render to the same path and the second gets a `_1` collision
suffix. Audiobookshelf treats a folder as exactly one library item, so
the pair becomes a single book with both files as tracks and a summed
runtime.

Shelfmark already parses and displays the language. It just never
reached the template engine.

## `{Language}` template variable

A template like `{Author}/{Title}{ (Language)}/{Author} - {Title}` now
yields:

```
/library/J K Rowling/Harry Potter (sv)/J K Rowling - Harry Potter.m4b
/library/J K Rowling/Harry Potter/J K Rowling - Harry Potter.m4b
```

The untagged edition's path is byte-identical to today, so no existing
layout shifts.

Three details worth flagging:

**The value is casefolded.** On a case-insensitive filesystem `(SV)` and
`(sv)` would collapse back into one folder, reintroducing the exact
collision being fixed.

**Values meaning "we don't know" render nothing** rather than producing
`Project Hail Mary (unknown)` folders. Anna's Archive reports that
string literally (`direct_download.py`, `language = detected or
"unknown"`).

**The frontend wasn't sending the release language at all**, so the
token would have stayed empty for exactly the audiobook sources in the
report. Prowlarr and AudiobookBay do not put language in `extra` the way
`direct_download` does, hence the payload plumbing. It reads
`release.language`, never `book.language` — the latter is the provider's
canonical edition and would mislabel a translation, with a regression
test for that specifically.

Not gated to audiobooks: Calibre-Web-Automated stages ingested files by
basename and discards folder structure, so the rename (filename)
template is the only lever those users have. Verified that form works:
`J K Rowling - Harry Potter (sv).epub`.

## Language consolidation (#1141)

Three release sources each carried their own alias map, all resolving to
the same ISO 639-1 codes, alongside a bundled database that only one of
them used. Adding a language meant editing three places.

Aliases now live in `data/book-languages.json` beside the code and name
they belong to, and `shelfmark/core/languages.py` resolves any of them —
two-letter code, ISO 639-2 three-letter in either the bibliographic or
terminological form, or English name. Prowlarr and AudiobookBay drop
their tables. Direct Download keeps its own path-parsing heuristics,
including the ambiguous short codes that collide with English words
(`de`, `en`, `no`, `in`), and takes only the alias data.

This also closes a coverage gap. MyAnonamouse offers 62 languages;
Prowlarr mapped 37, and an unmapped code is *dropped* rather than passed
through, so the other 25 carried no language at all — leaving
`{Language}` empty and the collision unfixed for Latin, Farsi, Tamil,
Urdu and the rest. Seven languages MAM offers had no database entry at
all: Bosnian, Burmese, Estonian, Icelandic, Manx, Scottish Gaelic,
Sanskrit.

Also fixes the Traditional Chinese code, which used a U+2011
non-breaking hyphen. Nothing compares against the ASCII spelling today
so it was latent, but it would silently defeat the first thing that did.

## Validation

Verified end to end against a live Prowlarr and MyAnonamouse, not just
unit tests. A real search returning both an English and a Swedish
edition, through the actual `queue_release` → `DownloadTask` → naming
path:

```
STEP 1  real MAM search        -> 37 releases, languages: ['en', 'sv']
STEP 3  queue_release          -> task.language='sv'
STEP 4  build_metadata_dict    -> metadata['Language']='sv'
STEP 5  build_library_path     -> /library/J K Rowling/Harry Potter (sv)/...
two language editions resolve to DIFFERENT folders: True
```

The refactor is pinned by a snapshot of both per-source maps taken
*before* they were deleted. All 131 aliases are asserted to still
resolve to the same code, one parametrised test each, so a regression
names the specific alias.

Also verified: the filename-only template, the retry round-trip
(`serialize_task_for_retry` → `_restore_task_from_retry_payload`, plus a
legacy payload with no `language` key), and placeholder handling.

Added a `KNOWN_TOKENS` ordering invariant test — `find_placeholder()`
does a substring `.find()` in list order and nothing protected that
contract, so a future token in the wrong position could silently shadow
an existing one. And a lockstep guard on the frontend, since
`KNOWN_TOKENS` is hand-duplicated in TypeScript.

**One caveat worth stating.** Three MAM codes are confirmed by
observation (`ENG`→`en`, `SWE`→`sv`, `MAL`→`ml`, the last from a real
`[MAL / EPUB]` Tagore release). The remaining ~59 are derived from ISO
639-2 rather than observed, because MAM's catalogue is overwhelmingly
English — enabling 27 extra languages still yielded only one non-English
hit across 258 results. Mitigated rather than closed: both 639-2
variants are present for every language where they differ, and a wrong
alias is an unused entry while a missing one loses the language. Happy
to correct any code a maintainer knows differs.

## Test results

2056 Python tests pass (up from 1906). Frontend typecheck, lint, format
and 126 unit tests pass.

Pre-existing failures on my machine, unchanged by this branch and
unrelated: `tests/bypass/` needs `seleniumbase`, and
`tests/config/test_entrypoint_permissions.py` uses bash-4 syntax that
macOS bash 3.2 rejects.

---------

Co-authored-by: delize <4028612+delize@users.noreply.github.com>
Co-authored-by: CaliBrain <calibrain@l4n.xyz>
2026-07-28 15:19:59 -04:00
dependabot[bot] d1cdaaeb5e Bump the python-deps group across 1 directory with 3 updates (#1144)
Bumps the python-deps group with 3 updates in the / directory:
[tqdm](https://github.com/tqdm/tqdm),
[seleniumbase](https://github.com/seleniumbase/SeleniumBase) and
[prek](https://github.com/j178/prek).

Updates `tqdm` from 4.69.0 to 4.69.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tqdm/tqdm/releases">tqdm's
releases</a>.</em></p>
<blockquote>
<h2>tqdm v4.69.1 stable</h2>
<ul>
<li><code>contrib.logging</code>: preserve filters (<a
href="https://redirect.github.com/tqdm/tqdm/issues/1775">#1775</a> &lt;-
<a
href="https://redirect.github.com/tqdm/tqdm/issues/1581">#1581</a>)</li>
<li><code>rich</code>: misc fixes (<a
href="https://redirect.github.com/tqdm/tqdm/issues/1773">#1773</a>)
<ul>
<li>support generators (without <code>len</code>) (<a
href="https://redirect.github.com/tqdm/tqdm/issues/1391">#1391</a>)</li>
<li>fix potential <code>AttributeError</code> on exit (<a
href="https://redirect.github.com/tqdm/tqdm/issues/1674">#1674</a>)</li>
</ul>
</li>
<li>fix <code>total=float('inf')</code> (<a
href="https://redirect.github.com/tqdm/tqdm/issues/1781">#1781</a> &lt;-
<a
href="https://redirect.github.com/tqdm/tqdm/issues/651">#651</a>)</li>
<li>fix potential <code>AttributeError</code> on exit (<a
href="https://redirect.github.com/tqdm/tqdm/issues/1778">#1778</a> &lt;-
<a href="https://redirect.github.com/tqdm/tqdm/issues/1668">#1668</a>,
<a href="https://redirect.github.com/tqdm/tqdm/issues/1537">#1537</a>,
<a
href="https://redirect.github.com/tqdm/tqdm/issues/261">#261</a>)</li>
<li>fix minor docs typos (<a
href="https://redirect.github.com/tqdm/tqdm/issues/1780">#1780</a>, <a
href="https://redirect.github.com/tqdm/tqdm/issues/1746">#1746</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/tqdm/tqdm/commit/93370ff891582e458c40196ea78a1a9d720fbb45"><code>93370ff</code></a>
bump version, merge pull requests <a
href="https://redirect.github.com/tqdm/tqdm/issues/1781">#1781</a>, <a
href="https://redirect.github.com/tqdm/tqdm/issues/1773">#1773</a>, <a
href="https://redirect.github.com/tqdm/tqdm/issues/1778">#1778</a>, <a
href="https://redirect.github.com/tqdm/tqdm/issues/1775">#1775</a>, <a
href="https://redirect.github.com/tqdm/tqdm/issues/1780">#1780</a>, <a
href="https://redirect.github.com/tqdm/tqdm/issues/1746">#1746</a></li>
<li><a
href="https://github.com/tqdm/tqdm/commit/238501fa4f86fae2ce268710dd9de0474d36c732"><code>238501f</code></a>
docs: fix redundant word typo in tests_synchronisation.py</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/b78624a9eacffcefa566e1bc6e476f6a03c6ec0a"><code>b78624a</code></a>
contrib.logging: preserve filters</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/9f92bc6439b906f359a9f533d527d7be0ac2f5b7"><code>9f92bc6</code></a>
remove unnecessary comments</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/494372e2be901951b230331870a458025ea1ea9b"><code>494372e</code></a>
Fix AttributeError in <code>close()</code> on failed
<code>__init__</code></li>
<li><a
href="https://github.com/tqdm/tqdm/commit/796cea14a9e43d40838fbe47c4e9287b30552042"><code>796cea1</code></a>
misc tidy</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/164ef7302b4c8c9dd455b523d8e8c38c61e68d50"><code>164ef73</code></a>
rich: misc fixes</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/f18c88e92707add150662885ec8d424e60c80bda"><code>f18c88e</code></a>
fix properly</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/90f59704b59c69a7ea1210a76ccffec58faa8538"><code>90f5970</code></a>
minor tidy</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/ef15578141f2df10f9c169ec619d8a5217f6a37f"><code>ef15578</code></a>
fix: handle total=inf in format_meter to avoid OverflowError</li>
<li>Additional commits viewable in <a
href="https://github.com/tqdm/tqdm/compare/v4.69.0...v4.69.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `seleniumbase` from 4.51.6 to 4.51.7
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.51.7 - Add <code>click_and_hold()</code> with CDP</h2>
<h2>Add <code>click_and_hold()</code> with CDP</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/6316b0aa95c947fc4f974662212f3cf77d1f829e">Add
a CDP-based click_and_hold() method</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/e667ad0ff5e19c8f85f7b7f42ba9f672a63639e3">Adjust
timings and refactor</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/f932c2d874c000d0149f2754a32bc955cfd71aa7">Update
examples</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/7775f1db77594bd1642714de013d999e49c36e5c">Update
the docs</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>Add <code>click_and_hold()</code> with CDP by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4441">seleniumbase/SeleniumBase#4441</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.51.6...v4.51.7">https://github.com/seleniumbase/SeleniumBase/compare/v4.51.6...v4.51.7</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/ec403eec792569e5d5dda5ff8d8772f1d9c141ae"><code>ec403ee</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4441">#4441</a>
from seleniumbase/click-and-hold-with-cdp</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/dc07cb87d21ca6991e1e156f6b60db2beb661039"><code>dc07cb8</code></a>
Version 4.51.7</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/7775f1db77594bd1642714de013d999e49c36e5c"><code>7775f1d</code></a>
Update the docs</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/f932c2d874c000d0149f2754a32bc955cfd71aa7"><code>f932c2d</code></a>
Update examples</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/e667ad0ff5e19c8f85f7b7f42ba9f672a63639e3"><code>e667ad0</code></a>
Adjust timings and refactor</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/6316b0aa95c947fc4f974662212f3cf77d1f829e"><code>6316b0a</code></a>
Add a CDP-based <code>click_and_hold()</code> method</li>
<li>See full diff in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.51.6...v4.51.7">compare
view</a></li>
</ul>
</details>
<br />

Updates `prek` from 0.4.10 to 0.4.11
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/releases">prek's
releases</a>.</em></p>
<blockquote>
<h2>0.4.11</h2>
<h2>Release Notes</h2>
<p>Released on 2026-07-25.</p>
<h3>Highlights</h3>
<ul>
<li>
<p>This release adds two new builtin hooks, <code>deny-pattern</code>
and <code>require-pattern</code>,
as native alternatives for <code>pygrep</code> use cases.
<code>deny-pattern</code> fails when a
configured pattern is found, while <code>require-pattern</code> ensures
every selected
file contains a match. By matching natively without spawning a Python
subprocess, they run over 4x faster than <code>pygrep</code> in
benchmarks. Note that
they use <a href="https://docs.rs/regex/latest/regex/#syntax">Rust
<code>regex</code> syntax</a>, which does
not support look-around features such as negative lookbehind.</p>
</li>
<li>
<p><code>prek run</code> now supports <code>--glob
&lt;PATTERN&gt;</code> to run hooks on tracked files
matching a glob. It can be repeated or combined with
<code>--files</code> and
<code>--directory</code>.</p>
</li>
<li>
<p>Hook priorities now support reusable aliases:</p>
<pre lang="toml"><code>[priorities]
checks = 10
<p>[[repos]]
repo = &quot;builtin&quot;
hooks = [
{ id = &quot;check-json&quot;, priority = &quot;checks&quot; },
{ id = &quot;check-yaml&quot;, priority = &quot;checks&quot; },
]
</code></pre></p>
<p>This makes parallel scheduling easier to read and maintain.</p>
</li>
</ul>
<h3>Enhancements</h3>
<ul>
<li>Add <code>deny-pattern</code> and <code>require-pattern</code>
builtin hooks (<a
href="https://redirect.github.com/j178/prek/pull/2359">#2359</a>)</li>
<li>Support <code>--glob</code> patterns in <code>prek run</code> (<a
href="https://redirect.github.com/j178/prek/pull/2381">#2381</a>)</li>
<li>Support reusable aliases for hook priorities (<a
href="https://redirect.github.com/j178/prek/pull/2331">#2331</a>)</li>
<li>Implement <code>requirements-txt-fixer</code> as a builtin hook (<a
href="https://redirect.github.com/j178/prek/pull/2390">#2390</a>)</li>
<li>Improve user-facing warnings and errors (<a
href="https://redirect.github.com/j178/prek/pull/2380">#2380</a>)</li>
<li>Install Node hooks through git url (<a
href="https://redirect.github.com/j178/prek/pull/2394">#2394</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Reduce blocking-pool overhead in file hooks (<a
href="https://redirect.github.com/j178/prek/pull/2384">#2384</a>)</li>
<li>Speed up mixed-line-ending scans with memchr2 (<a
href="https://redirect.github.com/j178/prek/pull/2391">#2391</a>)</li>
</ul>
<h3>Bug fixes</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/blob/master/CHANGELOG.md">prek's
changelog</a>.</em></p>
<blockquote>
<h2>0.4.11</h2>
<p>Released on 2026-07-25.</p>
<h3>Highlights</h3>
<ul>
<li>
<p>This release adds two new builtin hooks, <code>deny-pattern</code>
and <code>require-pattern</code>,
as native alternatives for <code>pygrep</code> use cases.
<code>deny-pattern</code> fails when a
configured pattern is found, while <code>require-pattern</code> ensures
every selected
file contains a match. By matching natively without spawning a Python
subprocess, they run over 4x faster than <code>pygrep</code> in
benchmarks. Note that
they use
<a href="https://docs.rs/regex/latest/regex/#syntax">Rust
<code>regex</code> syntax</a>, which does
not support look-around features such as negative lookbehind.</p>
</li>
<li>
<p><code>prek run</code> now supports <code>--glob
&lt;PATTERN&gt;</code> to run hooks on tracked files
matching a glob. It can be repeated or combined with
<code>--files</code> and
<code>--directory</code>.</p>
</li>
<li>
<p>Hook priorities now support reusable aliases:</p>
<pre lang="toml"><code>[priorities]
checks = 10
<p>[[repos]]
repo = &quot;builtin&quot;
hooks = [
{ id = &quot;check-json&quot;, priority = &quot;checks&quot; },
{ id = &quot;check-yaml&quot;, priority = &quot;checks&quot; },
]
</code></pre></p>
<p>This makes parallel scheduling easier to read and maintain.</p>
</li>
</ul>
<h3>Enhancements</h3>
<ul>
<li>Add <code>deny-pattern</code> and <code>require-pattern</code>
builtin hooks (<a
href="https://redirect.github.com/j178/prek/pull/2359">#2359</a>)</li>
<li>Support <code>--glob</code> patterns in <code>prek run</code> (<a
href="https://redirect.github.com/j178/prek/pull/2381">#2381</a>)</li>
<li>Support reusable aliases for hook priorities (<a
href="https://redirect.github.com/j178/prek/pull/2331">#2331</a>)</li>
<li>Implement <code>requirements-txt-fixer</code> as a builtin hook (<a
href="https://redirect.github.com/j178/prek/pull/2390">#2390</a>)</li>
<li>Improve user-facing warnings and errors (<a
href="https://redirect.github.com/j178/prek/pull/2380">#2380</a>)</li>
<li>Install Node hooks through git url (<a
href="https://redirect.github.com/j178/prek/pull/2394">#2394</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Reduce blocking-pool overhead in file hooks (<a
href="https://redirect.github.com/j178/prek/pull/2384">#2384</a>)</li>
<li>Speed up mixed-line-ending scans with memchr2 (<a
href="https://redirect.github.com/j178/prek/pull/2391">#2391</a>)</li>
</ul>
<h3>Bug fixes</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/j178/prek/commit/92ba6c8c21e7acf5ce056b7c9b8dcba7325c3f5a"><code>92ba6c8</code></a>
Bump version to 0.4.11 (<a
href="https://redirect.github.com/j178/prek/issues/2402">#2402</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/f575e428961414bc079060061bc570b71e553942"><code>f575e42</code></a>
Bump quinn-proto from 0.11.14 to 0.11.16 (<a
href="https://redirect.github.com/j178/prek/issues/2401">#2401</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/c0ed56d92af1530e0acc98da2a8544bcf1dd01e7"><code>c0ed56d</code></a>
Preserve system download policy when applying metadata (<a
href="https://redirect.github.com/j178/prek/issues/2395">#2395</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/3b8b5c53ea8ce674112e22f9f84addf3637ca9e0"><code>3b8b5c5</code></a>
Install Node hooks through git url (<a
href="https://redirect.github.com/j178/prek/issues/2394">#2394</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/ff11cbb2d30478d9b2607dd1886cf90e33979365"><code>ff11cbb</code></a>
Implement <code>requirements-txt-fixer</code> as a builtin hook (<a
href="https://redirect.github.com/j178/prek/issues/2390">#2390</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/c7dfc32c27f74ee04f9a4dc47b842fbb3e417631"><code>c7dfc32</code></a>
Match identify tags across filename parts (<a
href="https://redirect.github.com/j178/prek/issues/2399">#2399</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/928dc5cf3b7c7486664d6a7e34ce8d89a603d7f1"><code>928dc5c</code></a>
Speed up mixed-line-ending scans with memchr2 (<a
href="https://redirect.github.com/j178/prek/issues/2391">#2391</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/6c898499d8b7741f7e6b60cc42b195660e6d1afa"><code>6c89849</code></a>
Honor filenames in builtin hook entry and args (<a
href="https://redirect.github.com/j178/prek/issues/2389">#2389</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/28bc3f013e0c97c39834f2f02a708355ed642462"><code>28bc3f0</code></a>
Add description for no-commit-to-branch (<a
href="https://redirect.github.com/j178/prek/issues/2388">#2388</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/1d9adddb83df05770ea2a5562f141171be174d77"><code>1d9addd</code></a>
Reduce blocking-pool overhead in file hooks (<a
href="https://redirect.github.com/j178/prek/issues/2384">#2384</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/j178/prek/compare/v0.4.10...v0.4.11">compare
view</a></li>
</ul>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-28 01:23:47 -04:00
CaliBrain a1367f431d Ship Prowlarr per-entry rows as opt-in, not the default (#1145)
#1140 fixed the guid-only dedup that hid results from filter-specific
indexer entries, but shipped the new behaviour on by default:
PROWLARR_COLLAPSE_DUPLICATES defaulted off, so every existing Prowlarr
user got extra rows for any release that two indexer entries both
returned, and the setting only let them opt back into what they already
had.

Default it on. The dedup key stays indexer-qualified, so the entries are
still distinct internally; collapse then merges them back to one row,
resolved by the Prowlarr priority rather than by query order as before.
The visible result set matches what users had prior to #1140, and anyone
who wants the per-entry rows (freeleech and the like) turns the setting
off.

Beyond the noisier list, the default mattered because split rows differ
only by indexer name while sharing a title, size and peer count. Two of
them have distinct source_ids, so the queue's duplicate guard does not
fire, and the second grab's find_existing() matches the first by
infohash and runs post-processing over the same download again,
delivering the book twice.

The search-side fallback in config.get(..., True) is flipped to agree
with the field default. In production the field default governs, since
the config cache is seeded from the registry and the fallback only
applies to an unregistered key. The source tests monkeypatch config.get
with a plain dict lookup, though, so the fallback is what they exercise:
leaving it False would have kept every default-behaviour test asserting
the opposite of what ships. A new test pins the two together.

The deduplication tests now opt out explicitly, since they assert the
split itself. test_collapse_off_by_default_keeps_both_rows becomes a
pair, one for the untouched setting collapsing to a single row and one
for opting out.

docs/environment-variables.md is regenerated rather than hand-edited. It
was already stale on main, so it also picks up RTORRENT_AUDIOBOOK_LABEL,
DIRECT_DOWNLOAD_LANGUAGE_FROM_PATH, and reworded IRC_SEARCH_BOT and
RTORRENT_LABEL text from earlier merges.

The rest is fallout from the ruff 0.16.0 bump in #1139, which enabled a
much larger default rule set and started formatting Python code blocks
in Markdown: _find_existing_alias_user() uses min() instead of
sorted()[0] (FURB192, currently failing Python Quality on main), and the
two READMEs get their code blocks reformatted.
2026-07-28 01:23:31 -04:00
Andrew Doeringanddelize a4086f5e06 Keep Prowlarr results from distinct indexer entries separate (#1140)
Results were deduplicated on `guid` alone. When one tracker is
configured in Prowlarr as several indexer entries differing only by a
server-side search filter, all of them return the same guid for the same
torrent, so every entry but the first was silently discarded. Freeleech
and other filter-specific releases became invisible, replaced by the
unfiltered entry's copy, and which copy survived depended on query
ordering rather than user intent.

Include the indexer id in the dedup key so the entries stay distinct.

Release.source_id is qualified the same way. It keys the release cache
and becomes the download task id, so rows sharing a guid would otherwise
collide and a grab would route through whichever entry cached last,
defeating the point of showing them separately. The handler's task
matcher still accepts a bare guid or infoUrl so tasks queued before this
change still resolve.

Ordering now follows the priority already configured in Prowlarr (1-50,
lower preferred) rather than a new setting, since users curate that
ranking there and filtered entries are typically ranked ahead of their
unfiltered counterparts. The enabled-indexer list is fetched once and
reused for the enrichment check, so this costs no extra round trip.
Releases carry extra.indexer_priority, and the sort dropdown gains an
"Indexer priority" entry, ascending, alongside the existing alphabetical
"Indexer" sort; SortOption grew a default_direction for that, defaulting
to desc so "Peers" is unchanged.

PROWLARR_COLLAPSE_DUPLICATES (default off) optionally collapses a
release back to one row, resolved by the same Prowlarr priority. Left
off, every entry that carried a release keeps its own row, which is what
makes filtered results visible again.

Identity handling is defensive about partial payloads: a result that
cannot be identified is never dropped or merged, and collapse only
merges on a strong identifier (guid/downloadUrl/magnetUrl/infoUrl)
because merging on title alone would discard genuinely different
releases that share a name.

Fixes #1137

Co-authored-by: delize <4028612+delize@users.noreply.github.com>
2026-07-28 01:06:39 -04:00
Adam Vigneaux 0d02c6db47 Add qBittorrent API key authentication (#1143)
[qBittorrent

5.2.0](https://www.qbittorrent.org/news#sun-may-03rd-2026---qbittorrent-v5.2.0-release)
(May 2026) added support for API key-based authentication in addition to
the existing username/password-based authentication.

This commit adds support for qBittorrent API key authentication to
Shelfmark, configurable via environment variable or settings UI. If an
API key is set at the same time as the username/password, API key will
be preferred for authentication.

Requires `qbittorrent-api` 2026.5.3, the version that added the
`api_key` argument, or newer. `403 Forbidden` responses are not retried
with API key authentication because a retry has no chance of succeeding.

Tested end-to-end with my live qBittorrent 5.2.3 instance (WebAPI
v2.15.1).

<img width="785" height="616" alt="image"
src="https://github.com/user-attachments/assets/8064e10e-9a7f-49f0-804e-4c441d23a1fc"
/>
2026-07-28 00:54:19 -04:00
dependabot[bot] 7c457ff83e Bump the python-deps group with 4 updates (#1139)
Bumps the python-deps group with 4 updates:
[gevent](https://github.com/gevent/gevent),
[rarfile](https://github.com/markokr/rarfile),
[seleniumbase](https://github.com/seleniumbase/SeleniumBase) and
[ruff](https://github.com/astral-sh/ruff).

Updates `gevent` from 26.5.0 to 26.7.0
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/gevent/gevent/commit/1ae8449b77daeec72ef4247474f1888209a89c44"><code>1ae8449</code></a>
Preparing release 26.7.0</li>
<li><a
href="https://github.com/gevent/gevent/commit/af4fad4d0c9f005c1f875483ee72fcdac3b4fd95"><code>af4fad4</code></a>
Add note about change in binary wheel building.</li>
<li><a
href="https://github.com/gevent/gevent/commit/894a6e1a30ac1f81207d03a7d2e893126b8cd238"><code>894a6e1</code></a>
gevent/resolver/_addresses.py: Apply suggestion from <a
href="https://github.com/koteshyelamati"><code>@​koteshyelamati</code></a>
and move...</li>
<li><a
href="https://github.com/gevent/gevent/commit/4bbfae89de752f1032e76be0977bc9b83df0fa83"><code>4bbfae8</code></a>
Merge pull request <a
href="https://redirect.github.com/gevent/gevent/issues/2190">#2190</a>
from gevent/os-close-no-init-hub</li>
<li><a
href="https://github.com/gevent/gevent/commit/fbc0ca7ab234480165d7db008f140ed6efaa0f69"><code>fbc0ca7</code></a>
Blanket skip two SSL tests that are highly environment sensitive.</li>
<li><a
href="https://github.com/gevent/gevent/commit/bef4cdd10002b1d9cc3c855ac5bfbeab27999e3f"><code>bef4cdd</code></a>
Disable 3.15 on windows entirely for now; everything is crashing and I'm
pret...</li>
<li><a
href="https://github.com/gevent/gevent/commit/95c5b1e102f32530933b6b882687ce9242293925"><code>95c5b1e</code></a>
Attempt some CFFI-related workarounds.</li>
<li><a
href="https://github.com/gevent/gevent/commit/2b754a55f9555d7caca680935ec600a99c517059"><code>2b754a5</code></a>
cython won't build on windows, must stick to precompiled version.
Debugging a...</li>
<li><a
href="https://github.com/gevent/gevent/commit/ebcebb0fb426ed39b8a9724e376444c222a3d5b5"><code>ebcebb0</code></a>
Track down why 3.15 was initting the hub.</li>
<li><a
href="https://github.com/gevent/gevent/commit/dcf4eae4b36046d456c685efd95bc50cc085084e"><code>dcf4eae</code></a>
Merge pull request <a
href="https://redirect.github.com/gevent/gevent/issues/2189">#2189</a>
from ddorian/fix-shutdown-atexit-order</li>
<li>Additional commits viewable in <a
href="https://github.com/gevent/gevent/compare/26.5.0...26.7.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `rarfile` from 4.3 to 4.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/markokr/rarfile/releases">rarfile's
releases</a>.</em></p>
<blockquote>
<h2>rarfile v4.4</h2>
<p>Security fixes:</p>
<ul>
<li>Skip CRC check for some old subblocks. Previously <!-- raw HTML
omitted -->rarfile<!-- raw HTML omitted --> tried to calculate header
CRC by reading data payload for those, but that could cause excessive
allocations.</li>
</ul>
<p>Fixes:</p>
<ul>
<li>RAR5: Reattempt passwords after trying wrong password when header
encryption is used. [<a
href="https://redirect.github.com/markokr/rarfile/pull/131">#131</a>]</li>
<li>Restore fd position in file type detection functions. [<a
href="https://redirect.github.com/markokr/rarfile/pull/121">#121</a>]</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/markokr/rarfile/blob/master/doc/news.rst">rarfile's
changelog</a>.</em></p>
<blockquote>
<h2>Version 4.4 (2026-07-22)</h2>
<p>Security fixes:</p>
<ul>
<li>Skip CRC check for some old subblocks.  Previously
<code>rarfile</code> tried to calculate header CRC by reading
data payload for those, but that could cause excessive
allocations.</li>
</ul>
<p>Fixes:</p>
<ul>
<li>
<p>RAR5: Reattempt passwords after trying wrong password
when header encryption is used.
[<code>[#131](https://github.com/markokr/rarfile/issues/131)
&lt;https://github.com/markokr/rarfile/pull/131&gt;</code>_]</p>
</li>
<li>
<p>Restore fd position in file type detection functions.
[<code>[#121](https://github.com/markokr/rarfile/issues/121)
&lt;https://github.com/markokr/rarfile/pull/121&gt;</code>_]</p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/markokr/rarfile/commit/d106d2031e702bb187c746d9035362f0bd23a33c"><code>d106d20</code></a>
Refresh news.rst</li>
<li><a
href="https://github.com/markokr/rarfile/commit/8ab9f71684bddc60ba4631e4468727924b9816c1"><code>8ab9f71</code></a>
ci: restore CI on push</li>
<li><a
href="https://github.com/markokr/rarfile/commit/1b3db8971650236e7ddc8c80e705cf58511d2930"><code>1b3db89</code></a>
v4.4</li>
<li><a
href="https://github.com/markokr/rarfile/commit/c94c9691e3ba59816f0c0dc12fd4078d4639b7ad"><code>c94c969</code></a>
Skip CRC check for some old subblocks.</li>
<li><a
href="https://github.com/markokr/rarfile/commit/e2c0d6ef2b4b35ea20e10a76613b2aa79cdf784e"><code>e2c0d6e</code></a>
Reattempt passwords</li>
<li><a
href="https://github.com/markokr/rarfile/commit/cc6368edc1ccbdb80b619d9e962b8199b00dfed9"><code>cc6368e</code></a>
build(deps): bump tox from 4.53.1 to 4.56.1</li>
<li><a
href="https://github.com/markokr/rarfile/commit/0b3d4536a1012e1538157bc00638ebf68df3786a"><code>0b3d453</code></a>
dependabot: drop unnecessary etc</li>
<li><a
href="https://github.com/markokr/rarfile/commit/edb967cd0f531f8f49ad5106b36a028ead30fde6"><code>edb967c</code></a>
build(deps): bump actions/checkout from 4 to 7</li>
<li><a
href="https://github.com/markokr/rarfile/commit/79bd2e447823c18b8a99eeff9acea05cfbee9cd1"><code>79bd2e4</code></a>
build(deps): bump twine from 5.1.1 to 6.2.0</li>
<li><a
href="https://github.com/markokr/rarfile/commit/1c0c6a6b27c36c2a0306e33db71603950b43ee22"><code>1c0c6a6</code></a>
build(deps): bump actions/upload-artifact from 4 to 7</li>
<li>Additional commits viewable in <a
href="https://github.com/markokr/rarfile/compare/v4.3...v4.4">compare
view</a></li>
</ul>
</details>
<br />

Updates `seleniumbase` from 4.51.3 to 4.51.6
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.51.6 - CDP Mode: Patch 124</h2>
<h2>CDP Mode: Patch 124</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/7d79aa82eb7575706799716403088b7db2604100">Make
CDP Mode stealthier</a>
--&gt; Improve fingerprint on actions and add randomization</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/f98dd28d35df3e8e25f98fa2f1fc2c7a420d7287">Sync
up BaseCase and sb_cdp methods</a>
--&gt; (For <code>clear()</code> &amp; <code>clear_input()</code>, which
are the same)</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/ea68a2eca2b3142f941d9da8537c2cc0e917afaf">Refresh
Python dependencies</a>
--&gt; (Includes <code>certifi</code> and a few optional
dependencies)</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/e5bbe21beb6817e9bb43ce23ba899e62dc6b0723">Update
CDP Mode examples</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>CDP Mode: Patch 124 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4439">seleniumbase/SeleniumBase#4439</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.51.5...v4.51.6">https://github.com/seleniumbase/SeleniumBase/compare/v4.51.5...v4.51.6</a></p>
<h2>4.51.5 - CDP Mode: Patch 123</h2>
<h2>CDP Mode: Patch 123</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/a6e951c73c3e2b2ce1082671813692cba2d61a44">Make
CDP Mode super stealthy</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/e08ed3a3baeba129ad2ecc155b5c9c26df8a1ea1">Refresh
Python dependencies</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/1feb0db4187160e290595dfc6f05653c0f0fcc1e">Update
CDP Mode examples</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>CDP Mode: Patch 123 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4438">seleniumbase/SeleniumBase#4438</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.51.4...v4.51.5">https://github.com/seleniumbase/SeleniumBase/compare/v4.51.4...v4.51.5</a></p>
<h2>4.51.4 - CDP Mode: Patch 122</h2>
<h2>CDP Mode: Patch 122</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/ae9df3ea9f6340474e8aacf2e7b952342ec53088">Update
CDP Mode</a>
--&gt; For a stealthier fingerprint</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/a8b84ff9f8051a52ae72e81dfb6aab3d62458459">Refresh
Python dependencies</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/6b6599ac6689c7efe4e5e784814c7d4151de0652">Update
CDP Mode examples</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>CDP Mode: Patch 122 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4435">seleniumbase/SeleniumBase#4435</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.51.3...v4.51.4">https://github.com/seleniumbase/SeleniumBase/compare/v4.51.3...v4.51.4</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/74c0594ed79332667a1792cf01e28cc34135f57c"><code>74c0594</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4439">#4439</a>
from seleniumbase/cdp-mode-patch-124</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/cee06e0934db22fb144019e18b8f3e933c431eb8"><code>cee06e0</code></a>
Version 4.51.6</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/e5bbe21beb6817e9bb43ce23ba899e62dc6b0723"><code>e5bbe21</code></a>
Update CDP Mode examples</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/ea68a2eca2b3142f941d9da8537c2cc0e917afaf"><code>ea68a2e</code></a>
Refresh Python dependencies</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/f98dd28d35df3e8e25f98fa2f1fc2c7a420d7287"><code>f98dd28</code></a>
Sync up BaseCase and sb_cdp methods</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/7d79aa82eb7575706799716403088b7db2604100"><code>7d79aa8</code></a>
Make CDP Mode stealthier</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/0dda01d2b0690e77e2a5884c1ab7000cf33a8efb"><code>0dda01d</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4438">#4438</a>
from seleniumbase/cdp-mode-patch-123</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/2897f0a992e0cffa6d0af45ac8d2b85416cac26b"><code>2897f0a</code></a>
Version 4.51.5</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/e08ed3a3baeba129ad2ecc155b5c9c26df8a1ea1"><code>e08ed3a</code></a>
Refresh Python dependencies</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/1feb0db4187160e290595dfc6f05653c0f0fcc1e"><code>1feb0db</code></a>
Update CDP Mode examples</li>
<li>Additional commits viewable in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.51.3...v4.51.6">compare
view</a></li>
</ul>
</details>
<br />

Updates `ruff` from 0.15.22 to 0.16.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/releases">ruff's
releases</a>.</em></p>
<blockquote>
<h2>0.16.0</h2>
<h2>Release Notes</h2>
<p>Released on 2026-07-23.</p>
<p>Check out the <a href="https://astral.sh/blog/ruff-v0.16.0">blog
post</a> for a migration guide and overview of the changes!</p>
<h3>Breaking changes</h3>
<ul>
<li>
<p>Ruff now enables a much larger set of rules by default (413, up from
59). See the blog post for more details and the new <a
href="https://docs.astral.sh/ruff/default-rules/">Default Rules</a> page
for a full listing of the enabled rules.</p>
</li>
<li>
<p>Ruff can now format Python code blocks in Markdown files and will do
this by default. See the <a
href="https://docs.astral.sh/ruff/formatter/#markdown-code-formatting">documentation</a>
for more details.</p>
</li>
<li>
<p>Ruff now supports <code>ruff: ignore</code> comments at the ends of
lines, like <code>noqa</code> comments, or on the line preceding a
diagnostic. For example, these both suppress an <a
href="https://docs.astral.sh/ruff/rules/unused-import/"><code>unused-import</code></a>
(<code>F401</code>) diagnostic:</p>
<pre lang="py"><code>import math  # ruff: ignore[F401]
<h1>ruff: ignore[F401]</h1>
<p>import os
</code></pre></p>
</li>
<li>
<p>Fixes are now shown in <code>check</code> and <code>format
--check</code> output:</p>
<pre lang="console"><code>❯ ruff format --check .
unformatted: File would be reformatted
 --&gt; try.md:1:1
  |
1 | ```python
  - import   math
2 + import math
3 | ```
  |
<p>1 file would be reformatted
</code></pre></p>
<p>This example also shows off the Markdown formatting.</p>
</li>
<li>
<p><code>format --check</code> now supports the same output formats as
the linter, including the <code>github</code> and <code>gitlab</code>
outputs for rendering annotations in CI:</p>
<pre lang="console"><code>❯ ruff format --check --output-format github .
::error title=ruff
(unformatted),file=try.md,line=2,col=8,endLine=2,endColumn=10::try.md:2:8:
unformatted: File would be reformatted
</code></pre>
<p>See the CLI help or <a
href="https://docs.astral.sh/ruff/settings/#output-format">documentation</a>
for the full list of supported formats.</p>
</li>
<li>
<p>The <code>filename</code>, <code>location</code>,
<code>end_location</code>, <code>fix.edits[].location</code>, and
<code>fix.edits[].end_location</code> fields in the JSON output format
may now be <code>null</code> rather than defaulting to the empty string
and row 1, column 1, respectively.</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's
changelog</a>.</em></p>
<blockquote>
<h2>0.16.0</h2>
<p>Released on 2026-07-23.</p>
<p>Check out the <a href="https://astral.sh/blog/ruff-v0.16.0">blog
post</a> for a migration
guide and overview of the changes!</p>
<h3>Breaking changes</h3>
<ul>
<li>
<p>Ruff now enables a much larger set of rules by default (413, up from
59). See the blog post for
more details and the new <a
href="https://docs.astral.sh/ruff/default-rules/">Default Rules</a> page
for a
full listing of the enabled rules.</p>
</li>
<li>
<p>Ruff can now format Python code blocks in Markdown files and will do
this by default. See the
<a
href="https://docs.astral.sh/ruff/formatter/#markdown-code-formatting">documentation</a>
for more details.</p>
</li>
<li>
<p>Ruff now supports <code>ruff: ignore</code> comments at the ends of
lines, like <code>noqa</code> comments, or on the line preceding a
diagnostic. For example, these both suppress an <a
href="https://docs.astral.sh/ruff/rules/unused-import/"><code>unused-import</code></a>
(<code>F401</code>) diagnostic:</p>
<pre lang="py"><code>import math  # ruff: ignore[F401]
<h1>ruff: ignore[F401]</h1>
<p>import os
</code></pre></p>
</li>
<li>
<p>Fixes are now shown in <code>check</code> and <code>format
--check</code> output:</p>
<pre lang="console"><code>❯ ruff format --check .
unformatted: File would be reformatted
 --&gt; try.md:1:1
  |
1 | ```python
  - import   math
2 + import math
3 | ```
  |
<p>1 file would be reformatted
</code></pre></p>
<p>This example also shows off the Markdown formatting.</p>
</li>
<li>
<p><code>format --check</code> now supports the same output formats as
the linter, including the <code>github</code> and
<code>gitlab</code> outputs for rendering annotations in CI:</p>
<pre lang="console"><code>❯ ruff format --check --output-format github .
::error title=ruff
(unformatted),file=try.md,line=2,col=8,endLine=2,endColumn=10::try.md:2:8:
unformatted: File would be reformatted
</code></pre>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ruff/commit/a2635fd8f39e1d34ce8074cb486809426148f3e9"><code>a2635fd</code></a>
Bump 0.16.0 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27136">#27136</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/34334491652f8ceca5246d15c5c5afe0d6bc77ae"><code>3433449</code></a>
[ty] Reuse full call diagnostics for implicit setter calls (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27115">#27115</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/22400709220931375e072ad5d7460b9fc781af78"><code>2240070</code></a>
Reflect <code>ruff: ignore</code> and <code>--add-ignore</code>
stabilization in documentation (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27">#27</a>...</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/17ef71142c52230b923dad46ee5554140fc3fd2e"><code>17ef711</code></a>
Stabilize <code>--add-ignore</code> (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27125">#27125</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/ef912bbbe466856aa4aac10ad2a8856eb3d5aef3"><code>ef912bb</code></a>
Add newly stabilized rules to defaults (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27055">#27055</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/b30f04023281b46f12011f13ce6b45c247e0d2e3"><code>b30f040</code></a>
Stabilize new default rules (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27035">#27035</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/bcd70c5f10ea97ed52a785d70e7f33b83b7c697a"><code>bcd70c5</code></a>
Exclude Markdown files from <code>format-dev</code> runs (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27052">#27052</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/87e51e2cbbaed376fc13dead40fd772361fa07c0"><code>87e51e2</code></a>
Fix <code>format --check</code> spans for syntax errors (<a
href="https://redirect.github.com/astral-sh/ruff/issues/27045">#27045</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/afe2723a348364ac7f4b9abd76fc67779490c05e"><code>afe2723</code></a>
[<code>flake8-gettext</code>] Stabilize qualified-name and built-in
binding resolution (...</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/a9702d8928344f77a41dbe535f655a69fb04e2df"><code>a9702d8</code></a>
[<code>flake8-bandit</code>] Stabilize string literal binding resolution
(<code>S310</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26944">#26944</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ruff/compare/0.15.22...0.16.0">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-27 12:26:06 -04:00
dependabot[bot] 00f3fa565c Bump typescript from 6.0.3 to 7.0.2 in /src/frontend (#1133)
Bumps [typescript](https://github.com/microsoft/TypeScript) from 6.0.3
to 7.0.2.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/microsoft/TypeScript/commits">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~microsoft1es">microsoft1es</a>, a new
releaser for typescript since your current version.</p>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-24 18:38:31 -04:00
CaliBrain 7fdaf3f67d Add Audiobook support to IRC (#1136) 2026-07-24 18:34:12 -04:00
dependabot[bot] e9f3b9a3ad Bump the npm-deps group in /src/frontend with 7 updates (#1132)
Bumps the npm-deps group in /src/frontend with 7 updates:

| Package | From | To |
| --- | --- | --- |
|
[@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite)
| `4.3.2` | `4.3.3` |
| [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) |
`6.26.0` | `6.27.0` |
| [oxfmt](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt) |
`0.58.0` | `0.59.0` |
| [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) |
`1.73.0` | `1.74.0` |
| [oxlint-tsgolint](https://github.com/oxc-project/tsgolint) | `0.24.0`
| `0.25.0` |
|
[tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss)
| `4.3.2` | `4.3.3` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) |
`8.1.4` | `8.1.5` |

Updates `@tailwindcss/vite` from 4.3.2 to 4.3.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tailwindlabs/tailwindcss/releases">@​tailwindcss/vite's
releases</a>.</em></p>
<blockquote>
<h2>v4.3.3</h2>
<h3>Fixed</h3>
<ul>
<li>Support <code>--watch --poll[=ms]</code> in
<code>@tailwindcss/cli</code> when filesystem events are unreliable or
unavailable (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20297">#20297</a>)</li>
<li>Canonicalization: match arbitrary hex colors against theme colors
case-insensitively (e.g. <code>bg-[#fff]</code> and
<code>bg-[#FFF]</code> → <code>bg-white</code>) (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20298">#20298</a>)</li>
<li>Prevent Preflight from overriding Firefox's native
<code>iframe:focus-visible</code> outline styles (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20292">#20292</a>)</li>
<li>Ensure <code>theme('colors.foo')</code> in JS plugins resolves
correctly when both <code>--color-foo</code> and
<code>--color-foo-bar</code> exist (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20299">#20299</a>)</li>
<li>Ensure fractional opacity modifiers work with named shadow sizes
like <code>shadow-sm/12.5</code>, <code>text-shadow-sm/12.5</code>,
<code>drop-shadow-sm/12.5</code>, and <code>inset-shadow-sm/12.5</code>
(<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20302">#20302</a>)</li>
<li>Parse selectors like <code>[data-foo]div</code> as two selectors
instead of one (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20303">#20303</a>)</li>
<li>Ensure <code>@tailwindcss/postcss</code> rebuilds when a
preprocessor like Sass changes the input CSS without changing the input
file on disk (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20310">#20310</a>)</li>
<li>Ensure CSS nesting is handled even when Lightning CSS isn't run,
such as in <code>@tailwindcss/browser</code> and Tailwind Play (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20124">#20124</a>)</li>
<li>Prevent achromatic theme colors from shifting hue when mixed in
polar color spaces like <code>oklch</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20314">#20314</a>)</li>
<li>Ensure <code>--spacing(0)</code> is optimized to <code>0px</code>
instead of <code>0</code> so it remains a <code>&lt;length&gt;</code>
when used in <code>calc(…)</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20319">#20319</a>)</li>
<li>Load <code>@parcel/watcher</code> only when needed in
<code>@tailwindcss/cli --watch</code> mode, so one-off builds and
<code>--watch --poll</code> work when <code>@parcel/watcher</code> can't
be loaded (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20325">#20325</a>)</li>
<li>Use explicit platform fonts instead of <code>system-ui</code> and
<code>ui-sans-serif</code> so CJK text respects the page's
<code>lang</code> attribute on Windows (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20318">#20318</a>)</li>
<li>Prevent <code>@tailwindcss/upgrade</code> from rewriting ignored
files when run from a subdirectory (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20329">#20329</a>)</li>
<li>Ensure earlier <code>@source</code> rules pointing to nested files
are scanned when later <code>@source</code> rules point to files in
parent folders (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20335">#20335</a>)</li>
<li>Prevent <code>@tailwindcss/vite</code> from triggering full page
reloads when scanned files are processed by Vite but haven't been loaded
as modules yet (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20336">#20336</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md">@​tailwindcss/vite's
changelog</a>.</em></p>
<blockquote>
<h2>[4.3.3] - 2026-07-16</h2>
<h3>Fixed</h3>
<ul>
<li>Support <code>--watch --poll[=ms]</code> in
<code>@tailwindcss/cli</code> when filesystem events are unreliable or
unavailable (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20297">#20297</a>)</li>
<li>Canonicalization: match arbitrary hex colors against theme colors
case-insensitively (e.g. <code>bg-[#fff]</code> and
<code>bg-[#FFF]</code> → <code>bg-white</code>) (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20298">#20298</a>)</li>
<li>Prevent Preflight from overriding Firefox's native
<code>iframe:focus-visible</code> outline styles (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20292">#20292</a>)</li>
<li>Ensure <code>theme('colors.foo')</code> in JS plugins resolves
correctly when both <code>--color-foo</code> and
<code>--color-foo-bar</code> exist (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20299">#20299</a>)</li>
<li>Ensure fractional opacity modifiers work with named shadow sizes
like <code>shadow-sm/12.5</code>, <code>text-shadow-sm/12.5</code>,
<code>drop-shadow-sm/12.5</code>, and <code>inset-shadow-sm/12.5</code>
(<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20302">#20302</a>)</li>
<li>Parse selectors like <code>[data-foo]div</code> as two selectors
instead of one (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20303">#20303</a>)</li>
<li>Ensure <code>@tailwindcss/postcss</code> rebuilds when a
preprocessor like Sass changes the input CSS without changing the input
file on disk (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20310">#20310</a>)</li>
<li>Ensure CSS nesting is handled even when Lightning CSS isn't run,
such as in <code>@tailwindcss/browser</code> and Tailwind Play (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20124">#20124</a>)</li>
<li>Prevent achromatic theme colors from shifting hue when mixed in
polar color spaces like <code>oklch</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20314">#20314</a>)</li>
<li>Ensure <code>--spacing(0)</code> is optimized to <code>0px</code>
instead of <code>0</code> so it remains a <code>&lt;length&gt;</code>
when used in <code>calc(…)</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20319">#20319</a>)</li>
<li>Load <code>@parcel/watcher</code> only when needed in
<code>@tailwindcss/cli --watch</code> mode, so one-off builds and
<code>--watch --poll</code> work when <code>@parcel/watcher</code> can't
be loaded (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20325">#20325</a>)</li>
<li>Use explicit platform fonts instead of <code>system-ui</code> and
<code>ui-sans-serif</code> so CJK text respects the page's
<code>lang</code> attribute on Windows (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20318">#20318</a>)</li>
<li>Prevent <code>@tailwindcss/upgrade</code> from rewriting ignored
files when run from a subdirectory (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20329">#20329</a>)</li>
<li>Ensure earlier <code>@source</code> rules pointing to nested files
are scanned when later <code>@source</code> rules point to files in
parent folders (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20335">#20335</a>)</li>
<li>Prevent <code>@tailwindcss/vite</code> from triggering full page
reloads when scanned files are processed by Vite but haven't been loaded
as modules yet (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20336">#20336</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/c2b24dd15fed1c59dd521bd86082f520c9f5ad0d"><code>c2b24dd</code></a>
4.3.3 (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite/issues/20334">#20334</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/bdcd7087b332d263353d46ed366b7b08040ded7a"><code>bdcd708</code></a>
Don't trigger a full page reload for scanned files that Vite processes
as mod...</li>
<li>See full diff in <a
href="https://github.com/tailwindlabs/tailwindcss/commits/v4.3.3/packages/@tailwindcss-vite">compare
view</a></li>
</ul>
</details>
<br />

Updates `knip` from 6.26.0 to 6.27.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/webpro-nl/knip/releases">knip's
releases</a>.</em></p>
<blockquote>
<h2>Release 6.27.0</h2>
<ul>
<li>Add Temporal plugin (98191a04aad0572bead191b101bfecef75067b0c)</li>
<li>Handle -d alias for react-email dir argument
(53d1fec3213db8d6a84dfa7a82050b122782e065)</li>
<li>Expand pnpm script references with forwarded arguments
(33e9799df39450033061b9c4c07daf22f5d15721)</li>
<li>Treat path-shaped script tokens as file references, extend known
global binaries (d9508c282ab81351beda4b2f859435b49f142c51)</li>
<li>Mark tsdown neverBundle dependencies as referenced
(5fd863ba12e68842f91ab1a94e7e30d1e571da92)</li>
<li>Support TanStack Start in tanstack-router plugin
(bd63cd8af88dac03a98aa17f1add322f7bdf4a94)</li>
<li>Mark vite ssr.external dependencies as referenced
(91de938032062cb667bced0eadef9e67d1e20b1f)</li>
<li>Expand bun, npm, and yarn script references with forwarded arguments
(b5a31767a83e2cec3fb20c3b146d94c0412a4fd6)</li>
<li>Add Latitude as sponsor
(42676759cad5fb10ac96fd7c0457037e906f4b68)</li>
<li>Regenerate plugin list in docs
(1d2cdffb4d4151f848c44e066eff43e9efbf44fe)</li>
<li>Support Vue 2 template auto-imports (close <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1881">#1881</a>)
(2157f3913f77216d268b2255899b6fc58609f9bb)</li>
<li>Ignore block-commented stylesheet imports (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1880">#1880</a>,
close <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1882">#1882</a>)
(a627f53221547d5397d70722fcc2fd723889e579)</li>
<li>Support TypeScript 5.0 narrowing for ssr.external
(989292f79b9d19028bfc7ba988b74ba691041a45)</li>
<li>Resolve local binaries from bun run
(5e9a7f0286e2c2175bb299c514cc7b67a7cf24f1)</li>
<li>Support Vitest inline project config extensions
(f682327f6181f44e3924715efa7404aa39e532f0)</li>
<li>Resolve package names from aliased node_modules paths
(c273d5fbc1b0d3eaf27b38c3f31d3a2a4b6b184a)</li>
<li>Support Tailwind CSS URL imports
(57a4770447cc4c8f9ea4ffb9171c0c92eed4f255)</li>
<li>Resolve unbuilt workspace package exports (close <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1877">#1877</a>)
(1b0a2c0ee784db165c7f92fe10397a6cb513c1cf)</li>
<li>Skip unresolved imports of gitignored generated files
(605c9049f73838beb1c6fee83c6a5421ae1a6d07)</li>
<li>Attribute imports of aliased packages to the declared dependency
(d05e775c9a3a5676dc9eadcc1093f7d9983efd06)</li>
<li>Flag unresolvable imports of transitive peer deps as unlisted
(resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1883">#1883</a>)
(18f0bff4ea683837f75ca744d3fbde52df49b297)</li>
<li>Don't count ambient module declarations in script files as imports
(e52aecfd7c022cbe72b1c11dde3f4968579ac409)</li>
<li>Update rolldown snapshot
(a63e92788239062682603a5727ede371db7845c8)</li>
<li>Remove obsolete workarounds from ecosystem tests
(a521cf790d726c18e644e2e316cdc09eb654f607)</li>
<li>Handle snapshotSerializers in Vitest plugin (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1884">#1884</a>)
(b597de951f6f0e897afdbbbb8a7d0c06c8d2437d)</li>
<li>Set the stage (c059c2be5803609924b852c2bf0cd90906a6a831)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/webpro-nl/knip/commit/e463b61a1fbbc53c559eb08c92aa751b5c838bc0"><code>e463b61</code></a>
Release knip@6.27.0</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/b597de951f6f0e897afdbbbb8a7d0c06c8d2437d"><code>b597de9</code></a>
Handle snapshotSerializers in Vitest plugin (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1884">#1884</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/e52aecfd7c022cbe72b1c11dde3f4968579ac409"><code>e52aecf</code></a>
Don't count ambient module declarations in script files as imports</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/18f0bff4ea683837f75ca744d3fbde52df49b297"><code>18f0bff</code></a>
Flag unresolvable imports of transitive peer deps as unlisted (resolve
<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1883">#1883</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/d05e775c9a3a5676dc9eadcc1093f7d9983efd06"><code>d05e775</code></a>
Attribute imports of aliased packages to the declared dependency</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/605c9049f73838beb1c6fee83c6a5421ae1a6d07"><code>605c904</code></a>
Skip unresolved imports of gitignored generated files</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/1b0a2c0ee784db165c7f92fe10397a6cb513c1cf"><code>1b0a2c0</code></a>
Resolve unbuilt workspace package exports (close <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1877">#1877</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/57a4770447cc4c8f9ea4ffb9171c0c92eed4f255"><code>57a4770</code></a>
Support Tailwind CSS URL imports</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/c273d5fbc1b0d3eaf27b38c3f31d3a2a4b6b184a"><code>c273d5f</code></a>
Resolve package names from aliased node_modules paths</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/f682327f6181f44e3924715efa7404aa39e532f0"><code>f682327</code></a>
Support Vitest inline project config extensions</li>
<li>Additional commits viewable in <a
href="https://github.com/webpro-nl/knip/commits/knip@6.27.0/packages/knip">compare
view</a></li>
</ul>
</details>
<br />

Updates `oxfmt` from 0.58.0 to 0.59.0
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/blob/main/npm/oxfmt/CHANGELOG.md">oxfmt's
changelog</a>.</em></p>
<blockquote>
<h2>[0.59.0] - 2026-07-13</h2>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>415fe1e oxfmt: Error on ignorePatterns that cannot match files
outside the config directory (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/24286">#24286</a>)
(leaysgur)</li>
</ul>
<h2>[0.55.0] - 2026-06-15</h2>
<h3>🚀 Features</h3>
<ul>
<li>9a2788b linter/unicorn: Implement <code>prefer-export-from</code>
rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/22935">#22935</a>)
(AliceLanniste)</li>
</ul>
<h2>[0.54.0] - 2026-06-08</h2>
<h3>📚 Documentation</h3>
<ul>
<li>dadafe3 oxlint, oxfmt: Mention migrate skills in npm READMEs (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/22965">#22965</a>)
(Boshen)</li>
<li>f88961a oxfmt: Annotate each config option with supported languages
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/22953">#22953</a>)
(leaysgur)</li>
</ul>
<h2>[0.52.0] - 2026-05-26</h2>
<h3>🚀 Features</h3>
<ul>
<li>16b8058 oxfmt: Support <code>vite-plus/resolveConfig</code> for
vite.config.ts (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/22454">#22454</a>)
(leaysgur)</li>
</ul>
<h2>[0.50.0] - 2026-05-15</h2>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>43b9978 formatter/sort_imports: Treat subpath imports as internal
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/22440">#22440</a>)
(leaysgur)</li>
</ul>
<h2>[0.49.0] - 2026-05-11</h2>
<h3>🚀 Features</h3>
<ul>
<li>6e8e818 oxfmt: Experimental .svelte support (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/21700">#21700</a>)
(leaysgur)</li>
</ul>
<h2>[0.45.0] - 2026-04-13</h2>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>50c389b oxfmt: Support <code>.editorconfig</code>
<code>quote_type</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/20989">#20989</a>)
(leaysgur)</li>
</ul>
<h2>[0.44.0] - 2026-04-06</h2>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>dd2df87 npm: Export package.json for oxlint and oxfmt (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/20784">#20784</a>)
(kazuya kawaguchi)</li>
<li>4216380 oxfmt: Support <code>.editorconfig</code>
<code>tab_width</code> fallback (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/20988">#20988</a>)
(leaysgur)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/commit/2d4e8d20644e0e7446f0a381894b45ea339a0625"><code>2d4e8d2</code></a>
release(apps): oxlint v1.74.0 &amp;&amp; oxfmt v0.59.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/24454">#24454</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/415fe1e7bb423cf05019c5e2c9a5705eebbc5447"><code>415fe1e</code></a>
fix(oxfmt): error on ignorePatterns that cannot match files outside the
confi...</li>
<li>See full diff in <a
href="https://github.com/oxc-project/oxc/commits/oxfmt_v0.59.0/npm/oxfmt">compare
view</a></li>
</ul>
</details>
<br />

Updates `oxlint` from 1.73.0 to 1.74.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/releases">oxlint's
releases</a>.</em></p>
<blockquote>
<h2>oxlint v1.27.0 &amp;&amp; oxfmt v0.12.0</h2>
<h1>Oxlint v1.27.0</h1>
<h3>🚀 Features</h3>
<ul>
<li>222a8f0 linter/plugins: Implement
<code>SourceCode#isSpaceBetween</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15498">#15498</a>)
(overlookmotel)</li>
<li>2f9735d linter/plugins: Implement
<code>context.languageOptions</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15486">#15486</a>)
(overlookmotel)</li>
<li>bc731ff linter/plugins: Stub out all <code>Context</code> APIs (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15479">#15479</a>)
(overlookmotel)</li>
<li>5822cb4 linter/plugins: Add <code>extend</code> method to
<code>FILE_CONTEXT</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15477">#15477</a>)
(overlookmotel)</li>
<li>7b1e6f3 apps: Add pure rust binaries and release to github (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15469">#15469</a>)
(Boshen)</li>
<li>2a89b43 linter: Introduce debug assertions after fixes to assert
validity (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15389">#15389</a>)
(camc314)</li>
<li>ad3c45a editor: Add <code>oxc.path.node</code> option (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15040">#15040</a>)
(Sysix)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>6f3cd77 linter/no-var: Incorrect warning for blocks (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15504">#15504</a>)
(Hamir Mahal)</li>
<li>6957fb9 linter/plugins: Do not allow access to
<code>Context#id</code> in <code>createOnce</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15489">#15489</a>)
(overlookmotel)</li>
<li>7409630 linter/plugins: Allow access to <code>cwd</code> in
<code>createOnce</code> in ESLint interop mode (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15488">#15488</a>)
(overlookmotel)</li>
<li>732205e parser: Reject <code>using</code> / <code>await using</code>
in a switch <code>case</code> / <code>default</code> clause (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15225">#15225</a>)
(sapphi-red)</li>
<li>a17ca32 linter/plugins: Replace <code>Context</code> class (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15448">#15448</a>)
(overlookmotel)</li>
<li>ecf2f7b language_server: Fail gracefully when tsgolint executable
not found (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15436">#15436</a>)
(camc314)</li>
<li>3c8d3a7 lang-server: Improve logging in failure case for tsgolint
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15299">#15299</a>)
(camc314)</li>
<li>ef71410 linter: Use jsx if source type is JS in fix debug assertion
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15434">#15434</a>)
(camc314)</li>
<li>e32bbf6 linter/no-var: Handle TypeScript declare keyword in fixer
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15426">#15426</a>)
(camc314)</li>
<li>6565dbe linter/switch-case-braces: Skip comments when searching for
<code>:</code> token (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15425">#15425</a>)
(camc314)</li>
<li>85bd19a linter/prefer-class-fields: Insert value after type
annotation in fixer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15423">#15423</a>)
(camc314)</li>
<li>fde753e linter/plugins: Block access to
<code>context.settings</code> in <code>createOnce</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15394">#15394</a>)
(overlookmotel)</li>
<li>ddd9f9f linter/forward-ref-uses-ref: Dont suggest removing wrapper
in invalid positions (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15388">#15388</a>)
(camc314)</li>
<li>dac2a9c linter/no-template-curly-in-string: Remove fixer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15387">#15387</a>)
(camc314)</li>
<li>989b8e3 linter/no-var: Only fix to <code>const</code> if the var has
an initializer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15385">#15385</a>)
(camc314)</li>
<li>cc403f5 linter/plugins: Return empty object for unimplemented
parserServices (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15364">#15364</a>)
(magic-akari)</li>
</ul>
<h3>⚡ Performance</h3>
<ul>
<li>25d577e language_server: Start tools in parallel (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15500">#15500</a>)
(Sysix)</li>
<li>3c57291 linter/plugins: Optimize loops (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15449">#15449</a>)
(overlookmotel)</li>
<li>3166233 linter/plugins: Remove <code>Arc</code>s (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15431">#15431</a>)
(overlookmotel)</li>
<li>9de1322 linter/plugins: Lazily deserialize settings JSON (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15395">#15395</a>)
(overlookmotel)</li>
<li>3049ec2 linter/plugins: Optimize <code>deepFreezeSettings</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15392">#15392</a>)
(overlookmotel)</li>
<li>444ebfd linter/plugins: Use single object for
<code>parserServices</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15378">#15378</a>)
(overlookmotel)</li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>97d2104 linter: Update comment in lint.rs about default value for
tsconfig path (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15530">#15530</a>)
(Connor Shea)</li>
<li>2c6bd9e linter: Always refer as &quot;ES2015&quot; instead of
&quot;ES6&quot; (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15411">#15411</a>)
(sapphi-red)</li>
<li>a0c5203 linter/import/named: Update &quot;ES7&quot; comment in
examples (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15410">#15410</a>)
(sapphi-red)</li>
<li>3dc24b5 linter,minifier: Always refer as &quot;ES Modules&quot;
instead of &quot;ES6 Modules&quot; (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15409">#15409</a>)
(sapphi-red)</li>
<li>2ad77fb linter/no-this-before-super: Correct &quot;Why is this
bad?&quot; section (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15408">#15408</a>)
(sapphi-red)</li>
<li>57f0ce1 linter: Add backquotes where appropriate (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15407">#15407</a>)
(sapphi-red)</li>
</ul>
<h1>Oxfmt v0.12.0</h1>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md">oxlint's
changelog</a>.</em></p>
<blockquote>
<h2>[1.74.0] - 2026-07-13</h2>
<h3>🚀 Features</h3>
<ul>
<li>0433a83 linter/eslint/no-inner-declarations: Add
<code>namespaces</code> option (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24044">#24044</a>)
(Boshen)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>8337835 linter: Error on <code>ignorePatterns</code> that cannot
match files aoutside the config directory (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24341">#24341</a>)
(leaysgur)</li>
<li>2ce5a33 linter: Resolve <code>ignorePatterns</code> relative to the
config dir (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24339">#24339</a>)
(leaysgur)</li>
</ul>
<h3>⚡ Performance</h3>
<ul>
<li>7f80cac linter/vue/prop-name-casing: Precompile
<code>ignoreProps</code> regex pattern (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24413">#24413</a>)
(connorshea)</li>
<li>6272051 linter/typescript/no-require-imports: Compile allow patterns
once (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24417">#24417</a>)
(connorshea)</li>
<li>33805b9 linter/jsdoc/require-param: Compile checkTypesPattern regex
once (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24420">#24420</a>)
(connorshea)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/commit/2d4e8d20644e0e7446f0a381894b45ea339a0625"><code>2d4e8d2</code></a>
release(apps): oxlint v1.74.0 &amp;&amp; oxfmt v0.59.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24454">#24454</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/7f80cac4fdfb9f488886267fe7a85ca2b73be85e"><code>7f80cac</code></a>
perf(linter/vue/prop-name-casing): precompile <code>ignoreProps</code>
regex pattern (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/2">#2</a>...</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/6272051fcd0b8ef168c8d9ec98df31cce85364b7"><code>6272051</code></a>
perf(linter/typescript/no-require-imports): compile allow patterns once
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24417">#24417</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/33805b96917e706adb02b373d53170297c238ce0"><code>33805b9</code></a>
perf(linter/jsdoc/require-param): compile checkTypesPattern regex once
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24420">#24420</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/0433a8376aa3e51ee7a8371f897553cddcf4a663"><code>0433a83</code></a>
feat(linter/eslint/no-inner-declarations): add <code>namespaces</code>
option (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24044">#24044</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/83378351e43ecd605c5a52cb1454cbed9421ac1a"><code>8337835</code></a>
fix(linter): error on <code>ignorePatterns</code> that cannot match
files aoutside the c...</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/2ce5a33eccb96d1e297b54f4e7c1fc83f67e9ae2"><code>2ce5a33</code></a>
fix(linter): resolve <code>ignorePatterns</code> relative to the config
dir (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24339">#24339</a>)</li>
<li>See full diff in <a
href="https://github.com/oxc-project/oxc/commits/oxlint_v1.74.0/npm/oxlint">compare
view</a></li>
</ul>
</details>
<br />

Updates `oxlint-tsgolint` from 0.24.0 to 0.25.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/tsgolint/releases">oxlint-tsgolint's
releases</a>.</em></p>
<blockquote>
<h2>v0.25.0</h2>
<h2>What's Changed</h2>
<ul>
<li>chore(deps): update pnpm to v11.9.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1043">oxc-project/tsgolint#1043</a></li>
<li>perf(no-unnecessary-type-parameters): reuse AST-walk visitor closure
by <a href="https://github.com/connorshea"><code>@​connorshea</code></a>
in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1034">oxc-project/tsgolint#1034</a></li>
<li>perf(strict-void-return): reuse function-body walk visitor closure
by <a href="https://github.com/connorshea"><code>@​connorshea</code></a>
in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1033">oxc-project/tsgolint#1033</a></li>
<li>chore: update typescript-go submodule by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1048">oxc-project/tsgolint#1048</a></li>
<li>docs: normalize README sponsor section by <a
href="https://github.com/Boshen"><code>@​Boshen</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1050">oxc-project/tsgolint#1050</a></li>
<li>chore: strip symbols from release binary by <a
href="https://github.com/Boshen"><code>@​Boshen</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1049">oxc-project/tsgolint#1049</a></li>
<li>chore: add -trimpath to release build by <a
href="https://github.com/Boshen"><code>@​Boshen</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1051">oxc-project/tsgolint#1051</a></li>
<li>chore(deps): update crate-ci/typos action to v1.48.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1052">oxc-project/tsgolint#1052</a></li>
<li>chore(deps): update github actions by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1056">oxc-project/tsgolint#1056</a></li>
<li>chore(deps): update npm packages by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1057">oxc-project/tsgolint#1057</a></li>
<li>fix(no-deprecated): avoid contextual union property false positives
by <a href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1059">oxc-project/tsgolint#1059</a></li>
<li>fix(prefer-optional-chain): preserve negated optional guard by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1061">oxc-project/tsgolint#1061</a></li>
<li>perf(linter): gate reportNodeWithSuggestions on FixSuggestions by <a
href="https://github.com/connorshea"><code>@​connorshea</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1067">oxc-project/tsgolint#1067</a></li>
<li>fix(switch-exhaustiveness-check): honor default case comment
patterns by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1070">oxc-project/tsgolint#1070</a></li>
<li>perf(strict-void-return): skip type queries for never-callable
expressions by <a
href="https://github.com/connorshea"><code>@​connorshea</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1068">oxc-project/tsgolint#1068</a></li>
<li>perf(no-unsafe-argument): skip type queries for never-unsafe
arguments by <a
href="https://github.com/connorshea"><code>@​connorshea</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1066">oxc-project/tsgolint#1066</a></li>
<li>Revert &quot;perf(strict-void-return): skip type queries for
never-callable expressions&quot; by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1071">oxc-project/tsgolint#1071</a></li>
<li>chore: update typescript-go submodule by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1072">oxc-project/tsgolint#1072</a></li>
<li>chore(deps): update dependency dprint-json to v0.23.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1080">oxc-project/tsgolint#1080</a></li>
<li>chore(deps): update taiki-e/install-action action to v2.83.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1082">oxc-project/tsgolint#1082</a></li>
<li>chore(deps): update gomod by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1081">oxc-project/tsgolint#1081</a></li>
<li>perf(no-unnecessary-type-arguments): skip signature resolution when
node has no type arguments by <a
href="https://github.com/connorshea"><code>@​connorshea</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1064">oxc-project/tsgolint#1064</a></li>
<li>refactor: miscellaneous code cleanup by <a
href="https://github.com/connorshea"><code>@​connorshea</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1077">oxc-project/tsgolint#1077</a></li>
<li>refactor: remove dead code from rules by <a
href="https://github.com/connorshea"><code>@​connorshea</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1079">oxc-project/tsgolint#1079</a></li>
<li>refactor: drop dead isFunction guards built on a bitwise ast.Kind
mask by <a
href="https://github.com/connorshea"><code>@​connorshea</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1078">oxc-project/tsgolint#1078</a></li>
<li>chore: update Node.js to 26.5.0 by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1087">oxc-project/tsgolint#1087</a></li>
<li>fix(no-unnecessary-type-assertion): avoid recursive unknown checks
by <a href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1086">oxc-project/tsgolint#1086</a></li>
<li>fix(no-unnecessary-type-assertions): generic array false positive by
<a href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1045">oxc-project/tsgolint#1045</a></li>
<li>fix(no-unnecessary-type-assertion): preserve generic write casts by
<a href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1089">oxc-project/tsgolint#1089</a></li>
<li>docs: update sponsor section by <a
href="https://github.com/Boshen"><code>@​Boshen</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1090">oxc-project/tsgolint#1090</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/oxc-project/tsgolint/compare/v0.24.0...v0.25.0">https://github.com/oxc-project/tsgolint/compare/v0.24.0...v0.25.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/1abf9a5ea5656daffd1fef16654626a37fdde794"><code>1abf9a5</code></a>
docs: update sponsor section</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/6ee7c11801c7afe054ca61cd35d76d84215621cb"><code>6ee7c11</code></a>
fix(no-unnecessary-type-assertion): preserve generic write casts (<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1089">#1089</a>)</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/e23cc8351b8e3698903a8a73d267b4b326a96e20"><code>e23cc83</code></a>
fix(no-unnecessary-type-assertions): generic array false positive (<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1045">#1045</a>)</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/c3269c01a0c894a31330e1b4c3bd4edc6eb7694b"><code>c3269c0</code></a>
fix(no-unnecessary-type-assertion): avoid recursive unknown checks (<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1086">#1086</a>)</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/c214d5c65f2533dfbbce00680282d71a54180ad8"><code>c214d5c</code></a>
chore: update Node.js to 26.5.0 (<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1087">#1087</a>)</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/002b81870918f65cb73b54ce8899e245b3d51110"><code>002b818</code></a>
refactor: drop dead isFunction guards built on a bitwise ast.Kind mask
(<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1078">#1078</a>)</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/d6a1cb214548dc858b3b228ada8e0f2e2cd60935"><code>d6a1cb2</code></a>
refactor: remove dead code from rules (<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1079">#1079</a>)</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/7823d5902901a02b004a01f3623fa42c22cb4f1f"><code>7823d59</code></a>
refactor: miscellaneous code cleanup (<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1077">#1077</a>)</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/fcf43ff2e64ad2aaa818177d6189ffdce0ba1c2d"><code>fcf43ff</code></a>
perf(no-unnecessary-type-arguments): skip signature resolution when node
has ...</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/68759141e030b9d053d97bbe6fe4faf7e52bd5e8"><code>6875914</code></a>
chore(deps): update gomod (<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1081">#1081</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/oxc-project/tsgolint/compare/v0.24.0...v0.25.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `tailwindcss` from 4.3.2 to 4.3.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tailwindlabs/tailwindcss/releases">tailwindcss's
releases</a>.</em></p>
<blockquote>
<h2>v4.3.3</h2>
<h3>Fixed</h3>
<ul>
<li>Support <code>--watch --poll[=ms]</code> in
<code>@tailwindcss/cli</code> when filesystem events are unreliable or
unavailable (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20297">#20297</a>)</li>
<li>Canonicalization: match arbitrary hex colors against theme colors
case-insensitively (e.g. <code>bg-[#fff]</code> and
<code>bg-[#FFF]</code> → <code>bg-white</code>) (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20298">#20298</a>)</li>
<li>Prevent Preflight from overriding Firefox's native
<code>iframe:focus-visible</code> outline styles (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20292">#20292</a>)</li>
<li>Ensure <code>theme('colors.foo')</code> in JS plugins resolves
correctly when both <code>--color-foo</code> and
<code>--color-foo-bar</code> exist (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20299">#20299</a>)</li>
<li>Ensure fractional opacity modifiers work with named shadow sizes
like <code>shadow-sm/12.5</code>, <code>text-shadow-sm/12.5</code>,
<code>drop-shadow-sm/12.5</code>, and <code>inset-shadow-sm/12.5</code>
(<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20302">#20302</a>)</li>
<li>Parse selectors like <code>[data-foo]div</code> as two selectors
instead of one (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20303">#20303</a>)</li>
<li>Ensure <code>@tailwindcss/postcss</code> rebuilds when a
preprocessor like Sass changes the input CSS without changing the input
file on disk (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20310">#20310</a>)</li>
<li>Ensure CSS nesting is handled even when Lightning CSS isn't run,
such as in <code>@tailwindcss/browser</code> and Tailwind Play (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20124">#20124</a>)</li>
<li>Prevent achromatic theme colors from shifting hue when mixed in
polar color spaces like <code>oklch</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20314">#20314</a>)</li>
<li>Ensure <code>--spacing(0)</code> is optimized to <code>0px</code>
instead of <code>0</code> so it remains a <code>&lt;length&gt;</code>
when used in <code>calc(…)</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20319">#20319</a>)</li>
<li>Load <code>@parcel/watcher</code> only when needed in
<code>@tailwindcss/cli --watch</code> mode, so one-off builds and
<code>--watch --poll</code> work when <code>@parcel/watcher</code> can't
be loaded (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20325">#20325</a>)</li>
<li>Use explicit platform fonts instead of <code>system-ui</code> and
<code>ui-sans-serif</code> so CJK text respects the page's
<code>lang</code> attribute on Windows (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20318">#20318</a>)</li>
<li>Prevent <code>@tailwindcss/upgrade</code> from rewriting ignored
files when run from a subdirectory (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20329">#20329</a>)</li>
<li>Ensure earlier <code>@source</code> rules pointing to nested files
are scanned when later <code>@source</code> rules point to files in
parent folders (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20335">#20335</a>)</li>
<li>Prevent <code>@tailwindcss/vite</code> from triggering full page
reloads when scanned files are processed by Vite but haven't been loaded
as modules yet (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20336">#20336</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md">tailwindcss's
changelog</a>.</em></p>
<blockquote>
<h2>[4.3.3] - 2026-07-16</h2>
<h3>Fixed</h3>
<ul>
<li>Support <code>--watch --poll[=ms]</code> in
<code>@tailwindcss/cli</code> when filesystem events are unreliable or
unavailable (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20297">#20297</a>)</li>
<li>Canonicalization: match arbitrary hex colors against theme colors
case-insensitively (e.g. <code>bg-[#fff]</code> and
<code>bg-[#FFF]</code> → <code>bg-white</code>) (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20298">#20298</a>)</li>
<li>Prevent Preflight from overriding Firefox's native
<code>iframe:focus-visible</code> outline styles (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20292">#20292</a>)</li>
<li>Ensure <code>theme('colors.foo')</code> in JS plugins resolves
correctly when both <code>--color-foo</code> and
<code>--color-foo-bar</code> exist (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20299">#20299</a>)</li>
<li>Ensure fractional opacity modifiers work with named shadow sizes
like <code>shadow-sm/12.5</code>, <code>text-shadow-sm/12.5</code>,
<code>drop-shadow-sm/12.5</code>, and <code>inset-shadow-sm/12.5</code>
(<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20302">#20302</a>)</li>
<li>Parse selectors like <code>[data-foo]div</code> as two selectors
instead of one (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20303">#20303</a>)</li>
<li>Ensure <code>@tailwindcss/postcss</code> rebuilds when a
preprocessor like Sass changes the input CSS without changing the input
file on disk (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20310">#20310</a>)</li>
<li>Ensure CSS nesting is handled even when Lightning CSS isn't run,
such as in <code>@tailwindcss/browser</code> and Tailwind Play (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20124">#20124</a>)</li>
<li>Prevent achromatic theme colors from shifting hue when mixed in
polar color spaces like <code>oklch</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20314">#20314</a>)</li>
<li>Ensure <code>--spacing(0)</code> is optimized to <code>0px</code>
instead of <code>0</code> so it remains a <code>&lt;length&gt;</code>
when used in <code>calc(…)</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20319">#20319</a>)</li>
<li>Load <code>@parcel/watcher</code> only when needed in
<code>@tailwindcss/cli --watch</code> mode, so one-off builds and
<code>--watch --poll</code> work when <code>@parcel/watcher</code> can't
be loaded (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20325">#20325</a>)</li>
<li>Use explicit platform fonts instead of <code>system-ui</code> and
<code>ui-sans-serif</code> so CJK text respects the page's
<code>lang</code> attribute on Windows (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20318">#20318</a>)</li>
<li>Prevent <code>@tailwindcss/upgrade</code> from rewriting ignored
files when run from a subdirectory (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20329">#20329</a>)</li>
<li>Ensure earlier <code>@source</code> rules pointing to nested files
are scanned when later <code>@source</code> rules point to files in
parent folders (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20335">#20335</a>)</li>
<li>Prevent <code>@tailwindcss/vite</code> from triggering full page
reloads when scanned files are processed by Vite but haven't been loaded
as modules yet (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20336">#20336</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/c2b24dd15fed1c59dd521bd86082f520c9f5ad0d"><code>c2b24dd</code></a>
4.3.3 (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20334">#20334</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/e48c5e80473c25fe5a27fe267fbaf4f6512424a3"><code>e48c5e8</code></a>
Fix weird character rendering on Windows with Japanese locale (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20318">#20318</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/35a3e9c5159bea77af0d48f0c8849279211cc7e9"><code>35a3e9c</code></a>
Always produce <code>\&lt;length&gt;</code> value when optimizing
<code>--spacing(0)</code> (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20319">#20319</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/4af47fbe942245fd05ad8cd4776c2dbd27237bee"><code>4af47fb</code></a>
Fix hues in achromatic theme colors to be <code>none</code> (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20314">#20314</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/5835691d210b1c530160c844a562c36fa5fcbdb3"><code>5835691</code></a>
Handle CSS nesting natively (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20124">#20124</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/67c745efde4c6b36879e812b4c3d9250f7e0b31e"><code>67c745e</code></a>
Fix bug in attribute selector parsing (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20303">#20303</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/2683903b86d695f9c2647a06eb58bd96ade1eaee"><code>2683903</code></a>
Support fractional opacity modifiers for named shadow sizes (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20302">#20302</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/04588b1e8f3dbc856d8a3fad97c460bee689dbf3"><code>04588b1</code></a>
Fix theme() in JS plugins returning unresolved object instead of DEFAULT
valu...</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/b53fa096c92d830fc64b7cf69581496242583446"><code>b53fa09</code></a>
fix: exclude iframes from focus-visible auto outline in Preflight (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20292">#20292</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/ef79119d4e2b8b0f37710e736605bd0b3419afb7"><code>ef79119</code></a>
Bump dependencies (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20300">#20300</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/tailwindlabs/tailwindcss/commits/v4.3.3/packages/tailwindcss">compare
view</a></li>
</ul>
</details>
<br />

Updates `vite` from 8.1.4 to 8.1.5
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/releases">vite's
releases</a>.</em></p>
<blockquote>
<h2>v8.1.5</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v8.1.5/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md">vite's
changelog</a>.</em></p>
<blockquote>
<h2><!-- raw HTML omitted --><a
href="https://github.com/vitejs/vite/compare/v8.1.4...v8.1.5">8.1.5</a>
(2026-07-16)<!-- raw HTML omitted --></h2>
<h3>Bug Fixes</h3>
<ul>
<li><strong>bundled-dev:</strong> avoid duplicated <code>buildEnd</code>
(<a
href="https://redirect.github.com/vitejs/vite/issues/22931">#22931</a>)
(<a
href="https://github.com/vitejs/vite/commit/810032097079be1a7da0e2b09ec9d92dd07ec13f">8100320</a>)</li>
<li><strong>client:</strong> overlay error message format align rolldown
(<a
href="https://redirect.github.com/vitejs/vite/issues/22869">#22869</a>)
(<a
href="https://github.com/vitejs/vite/commit/5a72b8780705b575026617e86b0b92dea63a56a5">5a72b87</a>)</li>
<li><strong>deps:</strong> update all non-major dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/22921">#22921</a>)
(<a
href="https://github.com/vitejs/vite/commit/fef682d3f067d534a559faf6fd9baedda2e9f8f1">fef682d</a>)</li>
<li><strong>deps:</strong> update rolldown-related dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/22922">#22922</a>)
(<a
href="https://github.com/vitejs/vite/commit/3c345e475a5546a1cc6374682af89caebfe9c593">3c345e4</a>)</li>
<li><strong>module-runner:</strong> don't crash stack-trace source
mapping when globalThis.Buffer is absent (<a
href="https://redirect.github.com/vitejs/vite/issues/22945">#22945</a>)
(<a
href="https://github.com/vitejs/vite/commit/f8b38e316bcefbf29f762f90ee49c88cd52c43b5">f8b38e3</a>)</li>
<li><strong>optimizer:</strong> respect importer module format for
dynamic import interop with CJS deps (<a
href="https://redirect.github.com/vitejs/vite/issues/22951">#22951</a>)
(<a
href="https://github.com/vitejs/vite/commit/6c08c39ac4fb5868d080a51ff976a44693fc56ab">6c08c39</a>)</li>
<li><strong>ssr:</strong> scope switch-case declarations to the switch,
not the function (<a
href="https://redirect.github.com/vitejs/vite/issues/22893">#22893</a>)
(<a
href="https://github.com/vitejs/vite/commit/b59a73f76f5557492d83d097bb33b3dd02f27d51">b59a73f</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li><strong>build:</strong> fix incorrect <code>@default</code> for
build.cssMinify (<a
href="https://redirect.github.com/vitejs/vite/issues/22948">#22948</a>)
(<a
href="https://github.com/vitejs/vite/commit/c88c236186d8e2384653e382dd0960ca1ff5b24c">c88c236</a>)</li>
<li><strong>build:</strong> fix incorrect <code>@default</code> for
build.lib.formats (<a
href="https://redirect.github.com/vitejs/vite/issues/22911">#22911</a>)
(<a
href="https://github.com/vitejs/vite/commit/369ed609a4aace3aee4e4194a54990694aa4e7ac">369ed60</a>)</li>
</ul>
<h3>Tests</h3>
<ul>
<li>avoid scanner scanning all files under <code>__tests__</code> (<a
href="https://redirect.github.com/vitejs/vite/issues/22912">#22912</a>)
(<a
href="https://github.com/vitejs/vite/commit/c961cae2868cc1521457ec60583867f0440e6949">c961cae</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/vitejs/vite/commit/5e7fe129a4dde4f41934083b25e490059985f4e6"><code>5e7fe12</code></a>
release: v8.1.5</li>
<li><a
href="https://github.com/vitejs/vite/commit/6c08c39ac4fb5868d080a51ff976a44693fc56ab"><code>6c08c39</code></a>
fix(optimizer): respect importer module format for dynamic import
interop wit...</li>
<li><a
href="https://github.com/vitejs/vite/commit/5a72b8780705b575026617e86b0b92dea63a56a5"><code>5a72b87</code></a>
fix(client): overlay error message format align rolldown (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22869">#22869</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/f8b38e316bcefbf29f762f90ee49c88cd52c43b5"><code>f8b38e3</code></a>
fix(module-runner): don't crash stack-trace source mapping when
globalThis.Bu...</li>
<li><a
href="https://github.com/vitejs/vite/commit/810032097079be1a7da0e2b09ec9d92dd07ec13f"><code>8100320</code></a>
fix(bundled-dev): avoid duplicated <code>buildEnd</code> (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22931">#22931</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/c88c236186d8e2384653e382dd0960ca1ff5b24c"><code>c88c236</code></a>
docs(build): fix incorrect <code>@default</code> for build.cssMinify (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22948">#22948</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/b59a73f76f5557492d83d097bb33b3dd02f27d51"><code>b59a73f</code></a>
fix(ssr): scope switch-case declarations to the switch, not the function
(<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22">#22</a>...</li>
<li><a
href="https://github.com/vitejs/vite/commit/fef682d3f067d534a559faf6fd9baedda2e9f8f1"><code>fef682d</code></a>
fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22921">#22921</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/3c345e475a5546a1cc6374682af89caebfe9c593"><code>3c345e4</code></a>
fix(deps): update rolldown-related dependencies (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22922">#22922</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/369ed609a4aace3aee4e4194a54990694aa4e7ac"><code>369ed60</code></a>
docs(build): fix incorrect <code>@default</code> for build.lib.formats
(<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22911">#22911</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/vitejs/vite/commits/v8.1.5/packages/vite">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-24 18:33:52 -04:00
dependabot[bot] 8eb9bd5c21 Bump python from d3400aa to cea0e60 (#1131)
Bumps python from `d3400aa` to `cea0e60`.


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=python&package-manager=docker&previous-version=3.14.6-slim&new-version=3.14.6-slim)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-24 18:33:45 -04:00
dependabot[bot] a4d587037d Bump seleniumbase from 4.51.2 to 4.51.3 in the python-deps group (#1128)
Bumps the python-deps group with 1 update:
[seleniumbase](https://github.com/seleniumbase/SeleniumBase).

Updates `seleniumbase` from 4.51.2 to 4.51.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.51.3 - CDP Mode: Patch 121</h2>
<h2>CDP Mode: Patch 121</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/a190a7e3f9360d04d14f31ecdb773b0d803babd1">Update
CDP Mode</a>
-- Add <code>scroll</code> &amp; <code>timeout</code> args for some
methods.
-- Handle a special case for clicking <code>svg</code> elements.</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/01c6cec2fedb22ea8afd98333341477716a886e9">Refresh
Python dependencies</a>
-- (<code>websockets</code>, <code>filelock</code>,
<code>platformdirs</code>)</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>CDP Mode: Patch 121 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4431">seleniumbase/SeleniumBase#4431</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.51.2...v4.51.3">https://github.com/seleniumbase/SeleniumBase/compare/v4.51.2...v4.51.3</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/2d5d3fafb22d894abeb941ab51b0428ad55cb48b"><code>2d5d3fa</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4431">#4431</a>
from seleniumbase/cdp-mode-patch-121</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/2cf34788cc111bcf415180dbbe4440490a776b9d"><code>2cf3478</code></a>
Version 4.51.3</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/01c6cec2fedb22ea8afd98333341477716a886e9"><code>01c6cec</code></a>
Refresh Python dependencies</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/a190a7e3f9360d04d14f31ecdb773b0d803babd1"><code>a190a7e</code></a>
Update CDP Mode</li>
<li>See full diff in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.51.2...v4.51.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=seleniumbase&package-manager=uv&previous-version=4.51.2&new-version=4.51.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-24 18:33:38 -04:00
Calibrain 1747b3466e Fix linting issues 2026-07-21 19:52:25 +00:00
CaliBrain 81a057c8a0 Try and fix OIDC (#1127) 2026-07-21 15:43:54 -04:00
Matt Van HornandMatt Van Horn 91b1064689 fix(naming): respect subfolder order when assigning audiobook part numbers (#1123)
## Summary

Audiobooks whose files are split across nested subfolders now keep their
on-disk folder order when "Rename and Organize" assigns part numbers.
Previously `natural_sort_key` keyed on `Path(path).name`, so identically
named files from different folders (`001.mp3` in `00_Introduction/` vs
`06_Side 6/`) collapsed together and the part sequence scrambled.

## Background

zazizou reported in #1007 that the total file count was right but
`06_Side 6/001.mp3` came out as part 1 instead of
`00_Introduction/001_About.mp3`. `scan.py` flattens every nested file
into one list and `assign_part_numbers` sorts it with
`natural_sort_key`, which discarded the parent-folder prefix. The fix
pads the existing `PAD_NUMBERS_PATTERN` numbers over the full relative
path (`str(path).lower()`) rather than the basename, so nested
audiobooks sort by folder first and then by filename. Flat directories
are unaffected because their files share a prefix, and
`natural_sort_key` is only called from `assign_part_numbers`, so nothing
else changes. I updated the test that pinned the old basename-only
ordering and added nested-subfolder and duplicate-basename cases;
`pytest tests/core/test_naming.py
tests/core/test_part_number_extraction.py` passes (102 tests).

Closes #1007

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-07-21 15:41:03 -04:00
dependabot[bot] c9a2b5eccf Bump the python-deps group across 1 directory with 4 updates (#1126)
Bumps the python-deps group with 4 updates in the / directory:
[tqdm](https://github.com/tqdm/tqdm),
[seleniumbase](https://github.com/seleniumbase/SeleniumBase),
[prek](https://github.com/j178/prek) and
[ruff](https://github.com/astral-sh/ruff).

Updates `tqdm` from 4.68.4 to 4.69.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tqdm/tqdm/releases">tqdm's
releases</a>.</em></p>
<blockquote>
<h2>tqdm v4.69.0 stable</h2>
<ul>
<li>add <code>tqdm.asyncio.gather(..., return_exceptions=False)</code>
(<a href="https://redirect.github.com/tqdm/tqdm/issues/1776">#1776</a>,
<a href="https://redirect.github.com/tqdm/tqdm/issues/1671">#1671</a>
&lt;- <a
href="https://redirect.github.com/tqdm/tqdm/issues/1286">#1286</a>)</li>
<li>misc minor framework updates
<ul>
<li>bump workflow actions &amp; pre-commit hooks</li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/tqdm/tqdm/commit/6ab24dcc5df910044f1f6e0685f95dbf9cd424f3"><code>6ab24dc</code></a>
bump version, workflow actions, pre-commit hooks</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/99615b978702dcf61986de62dfd252d82720af76"><code>99615b9</code></a>
asyncio: add gather(return_exceptions=False) (<a
href="https://redirect.github.com/tqdm/tqdm/issues/1776">#1776</a>)</li>
<li>See full diff in <a
href="https://github.com/tqdm/tqdm/compare/v4.68.4...v4.69.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `seleniumbase` from 4.51.0 to 4.51.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.51.2 - CDP Mode: Patch 120</h2>
<h2>CDP Mode: Patch 120</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/7614ca495d8a52a2100516c0fcad079e191c13f4">Fix
WebRTC leak for non-proxied navigation</a>
-- This resolves <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4428">seleniumbase/SeleniumBase#4428</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/92c528ae17d0e07294671d09cb253c5e5ebbb8ca">Drop
support for the deprecated Atlas Browser</a>
-- This resolves <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4427">seleniumbase/SeleniumBase#4427</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/9c0f401a9977efbb7d13114aaf98a52a0532af9c">Remove
Copilot examples (requires login now)</a>
-- This resolves <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4429">seleniumbase/SeleniumBase#4429</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/aca0363058c95c4590ff6639e4e51d05010ddbe8">Add
a CDP Mode fingerprint test</a>
-- (Uses the Clearcote fingerprint scanner / auditor)</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/e152f11df548e76ec34822188a5234f6cb09cd57">Update
Stealthy Playwright Mode examples</a>
-- (<code>sb.goto(url)</code> is stealthier than
<code>page.goto(url)</code>)</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/93de30adf71d6b3277512ab78f1ddf1eb0b586d3">Refresh
Python dependencies</a>
-- (<code>filelock</code> and <code>coverage</code>)</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>CDP Mode: Patch 120 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4430">seleniumbase/SeleniumBase#4430</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.51.1...v4.51.2">https://github.com/seleniumbase/SeleniumBase/compare/v4.51.1...v4.51.2</a></p>
<h2>4.51.1 - Updates to multiple areas</h2>
<h2>Updates to multiple areas</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/706e23c5b25000e64f9b97dbb9118717eafaed05">Update
the xpath-to-css converter</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/1c7fa3bbefd870e79e3b3e75044b2a88258f204e">Update
the CDP Mode ad-block list</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/cb6fbb103b4dc61d2f6d425c54dc966b4fd6181e">Update
error messages for proxy failures</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/fe392b5b3fd90a04d0d266ddbcd76cbcc7ccab7c">Update
proxy validation</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/dd14d5e0c7832150d70b6bd3bd950cc3a8ad10cb">Update
the Slider CAPTCHA solver</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/97d53f601faa2f8247eea00271ce6140e3d7d158">Update
CDP Mode examples</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>Updates to multiple areas by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4424">seleniumbase/SeleniumBase#4424</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.51.0...v4.51.1">https://github.com/seleniumbase/SeleniumBase/compare/v4.51.0...v4.51.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/f9fca2dc604067020363b285b0fff15910e64cea"><code>f9fca2d</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4430">#4430</a>
from seleniumbase/cdp-mode-patch-120</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/e4f803f5adc47880c5563c7ae528d1a190249865"><code>e4f803f</code></a>
Version 4.51.2</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/93de30adf71d6b3277512ab78f1ddf1eb0b586d3"><code>93de30a</code></a>
Refresh Python dependencies</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/e152f11df548e76ec34822188a5234f6cb09cd57"><code>e152f11</code></a>
Update Stealthy Playwright Mode examples</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/aca0363058c95c4590ff6639e4e51d05010ddbe8"><code>aca0363</code></a>
Add a CDP Mode fingerprint test</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/9c0f401a9977efbb7d13114aaf98a52a0532af9c"><code>9c0f401</code></a>
Remove Copilot examples (requires login now)</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/92c528ae17d0e07294671d09cb253c5e5ebbb8ca"><code>92c528a</code></a>
Drop support for the deprecated Atlas Browser</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/7614ca495d8a52a2100516c0fcad079e191c13f4"><code>7614ca4</code></a>
Fix WebRTC leak for non-proxied navigation</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/45a338be063e2ae0561779c1ca5433804242d98d"><code>45a338b</code></a>
Update CDP Mode examples</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/a1fd937c94825d1f668eacc457cb9539bfb22d93"><code>a1fd937</code></a>
Update CDP Mode examples</li>
<li>Additional commits viewable in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.51.0...v4.51.2">compare
view</a></li>
</ul>
</details>
<br />

Updates `prek` from 0.4.9 to 0.4.10
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/releases">prek's
releases</a>.</em></p>
<blockquote>
<h2>0.4.10</h2>
<h2>Release Notes</h2>
<p>Released on 2026-07-16.</p>
<h3>Enhancements</h3>
<ul>
<li>Add PHP language support (<a
href="https://redirect.github.com/j178/prek/pull/2314">#2314</a>)</li>
<li>Add freeze option to update settings (<a
href="https://redirect.github.com/j178/prek/pull/2323">#2323</a>)</li>
<li>Add tag filters to update configuration (<a
href="https://redirect.github.com/j178/prek/pull/2354">#2354</a>)</li>
<li>Identify 'mts' and 'cts' as TypeScript files (<a
href="https://redirect.github.com/j178/prek/pull/2209">#2209</a>)</li>
<li>Publish Alpine Docker images (<a
href="https://redirect.github.com/j178/prek/pull/2352">#2352</a>)</li>
<li>Support builtin and meta in try-repo (<a
href="https://redirect.github.com/j178/prek/pull/2350">#2350</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Fix Python discovery order (<a
href="https://redirect.github.com/j178/prek/pull/2348">#2348</a>)</li>
<li>Fix Windows progress rendering (<a
href="https://redirect.github.com/j178/prek/pull/2328">#2328</a>)</li>
<li>Preserve configured repo values for updates (<a
href="https://redirect.github.com/j178/prek/pull/2324">#2324</a>)</li>
<li>Scope synthetic <code>GIT_WORK_TREE</code> to git commands (<a
href="https://redirect.github.com/j178/prek/pull/2356">#2356</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Expand common workflows guide (<a
href="https://redirect.github.com/j178/prek/pull/2351">#2351</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/j178"><code>@​j178</code></a></li>
</ul>
<h2>Install prek 0.4.10</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://github.com/j178/prek/releases/download/v0.4.10/prek-installer.sh
| sh
</code></pre>
<h3>Install prebuilt binaries via powershell script</h3>
<pre lang="sh"><code>powershell -ExecutionPolicy Bypass -c &quot;irm
https://github.com/j178/prek/releases/download/v0.4.10/prek-installer.ps1
| iex&quot;
</code></pre>
<h3>Install prebuilt binaries via Homebrew</h3>
<pre lang="sh"><code>brew install prek
</code></pre>
<h2>Download prek 0.4.10</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/blob/master/CHANGELOG.md">prek's
changelog</a>.</em></p>
<blockquote>
<h2>0.4.10</h2>
<p>Released on 2026-07-16.</p>
<h3>Enhancements</h3>
<ul>
<li>Add PHP language support (<a
href="https://redirect.github.com/j178/prek/pull/2314">#2314</a>)</li>
<li>Add freeze option to update settings (<a
href="https://redirect.github.com/j178/prek/pull/2323">#2323</a>)</li>
<li>Add tag filters to update configuration (<a
href="https://redirect.github.com/j178/prek/pull/2354">#2354</a>)</li>
<li>Identify 'mts' and 'cts' as TypeScript files (<a
href="https://redirect.github.com/j178/prek/pull/2209">#2209</a>)</li>
<li>Publish Alpine Docker images (<a
href="https://redirect.github.com/j178/prek/pull/2352">#2352</a>)</li>
<li>Support builtin and meta in try-repo (<a
href="https://redirect.github.com/j178/prek/pull/2350">#2350</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Fix Python discovery order (<a
href="https://redirect.github.com/j178/prek/pull/2348">#2348</a>)</li>
<li>Fix Windows progress rendering (<a
href="https://redirect.github.com/j178/prek/pull/2328">#2328</a>)</li>
<li>Preserve configured repo values for updates (<a
href="https://redirect.github.com/j178/prek/pull/2324">#2324</a>)</li>
<li>Scope synthetic <code>GIT_WORK_TREE</code> to git commands (<a
href="https://redirect.github.com/j178/prek/pull/2356">#2356</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Expand common workflows guide (<a
href="https://redirect.github.com/j178/prek/pull/2351">#2351</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/j178"><code>@​j178</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/j178/prek/commit/b45e6710082ed28943c96268d0ea99026dbb13ba"><code>b45e671</code></a>
Bump version to 0.4.10 (<a
href="https://redirect.github.com/j178/prek/issues/2358">#2358</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/0cf3be5280613835759de9912ec57eb724d3db22"><code>0cf3be5</code></a>
Scope synthetic <code>GIT_WORK_TREE</code> to git commands (<a
href="https://redirect.github.com/j178/prek/issues/2356">#2356</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/0265dddfff97f1be7fcf3aaeefdeeb12572473de"><code>0265ddd</code></a>
Add tag filters to update configuration (<a
href="https://redirect.github.com/j178/prek/issues/2354">#2354</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/dcfa91b31b3b165bee4bc472291aadaf7910069f"><code>dcfa91b</code></a>
Publish Alpine Docker images (<a
href="https://redirect.github.com/j178/prek/issues/2352">#2352</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/1e0b2c108458e781c827695bd075406a13f15cb6"><code>1e0b2c1</code></a>
Expand common workflows guide (<a
href="https://redirect.github.com/j178/prek/issues/2351">#2351</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/753e13c4fe94d83695da6bc16f2939770c9aa219"><code>753e13c</code></a>
Support builtin and meta in try-repo (<a
href="https://redirect.github.com/j178/prek/issues/2350">#2350</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/1e2abfb81f5213990413f222ad4d7444af430518"><code>1e2abfb</code></a>
Fix Python discovery order (<a
href="https://redirect.github.com/j178/prek/issues/2348">#2348</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/ec15d683e1b9bd8e265d92d7116bf19b574fedc9"><code>ec15d68</code></a>
Update Rust crate indicatif to v0.18.6 (<a
href="https://redirect.github.com/j178/prek/issues/2330">#2330</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/ba4939a4856803ea1c4b886d5c8b3b9ba19df74a"><code>ba4939a</code></a>
Update Rust crate fs-err to v3.3.1 (<a
href="https://redirect.github.com/j178/prek/issues/2329">#2329</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/e43acba74e33728d30277a2c229eaed1def17b49"><code>e43acba</code></a>
Improve contribution templates (<a
href="https://redirect.github.com/j178/prek/issues/2347">#2347</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/j178/prek/compare/v0.4.9...v0.4.10">compare
view</a></li>
</ul>
</details>
<br />

Updates `ruff` from 0.15.21 to 0.15.22
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/releases">ruff's
releases</a>.</em></p>
<blockquote>
<h2>0.15.22</h2>
<h2>Release Notes</h2>
<p>Released on 2026-07-16.</p>
<h3>Preview features</h3>
<ul>
<li>[<code>pycodestyle</code>] Add an autofix for <code>E402</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/22212">#22212</a>)</li>
<li>[<code>refurb</code>] Allow subclassing builtins in stub files
(<code>FURB189</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26812">#26812</a>)</li>
<li>[<code>ruff</code>] Add rule to replace <code>noqa</code> comments
with <code>ruff:ignore</code> (<code>RUF105</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26423">#26423</a>)</li>
<li>[<code>ruff</code>] Add rule to use human-readable names in
<code>ruff:ignore</code> comments (<code>RUF106</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26682">#26682</a>)</li>
<li>[<code>ruff</code>] Add rule to use human-readable names in
configuration selectors (<code>RUF201</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26772">#26772</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>[<code>flake8-pyi</code>] Fix false positive in <code>__all__</code>
(<code>PYI053</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26872">#26872</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>pylint</code>] Ignore mutable type updates in
<code>redefined-loop-name</code> (<code>PLW2901</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25733">#25733</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Avoid redundant lexer token bookkeeping (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26765">#26765</a>)</li>
<li>Avoid redundant pending-indentation writes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26774">#26774</a>)</li>
<li>Avoid unnecessary identifier lookahead (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26525">#26525</a>)</li>
<li>Reuse parser scratch buffers (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26798">#26798</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Document argfile support (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26803">#26803</a>)</li>
<li>[<code>flake8-datetimez</code>] Clarify naming guidance for
<code>datetime.today</code> (<code>DTZ002</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26658">#26658</a>)</li>
<li>[<code>pycodestyle</code>] Document <code>E731</code> fix safety (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26847">#26847</a>)</li>
<li>[<code>ruff</code>] Clarify intentional async contexts for
<code>unused-async</code> (<code>RUF029</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26641">#26641</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/dwego"><code>@​dwego</code></a></li>
<li><a
href="https://github.com/MichaReiser"><code>@​MichaReiser</code></a></li>
<li><a href="https://github.com/Joosboy"><code>@​Joosboy</code></a></li>
<li><a
href="https://github.com/KaufmanDmitriy"><code>@​KaufmanDmitriy</code></a></li>
<li><a
href="https://github.com/PeterJCLaw"><code>@​PeterJCLaw</code></a></li>
<li><a href="https://github.com/ntBre"><code>@​ntBre</code></a></li>
<li><a
href="https://github.com/charliermarsh"><code>@​charliermarsh</code></a></li>
</ul>
<h2>Install ruff 0.15.22</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>&lt;/tr&gt;&lt;/table&gt; 
</code></pre>
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's
changelog</a>.</em></p>
<blockquote>
<h2>0.15.22</h2>
<p>Released on 2026-07-16.</p>
<h3>Preview features</h3>
<ul>
<li>[<code>pycodestyle</code>] Add an autofix for <code>E402</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/22212">#22212</a>)</li>
<li>[<code>refurb</code>] Allow subclassing builtins in stub files
(<code>FURB189</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26812">#26812</a>)</li>
<li>[<code>ruff</code>] Add rule to replace <code>noqa</code> comments
with <code>ruff:ignore</code> (<code>RUF105</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26423">#26423</a>)</li>
<li>[<code>ruff</code>] Add rule to use human-readable names in
<code>ruff:ignore</code> comments (<code>RUF106</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26682">#26682</a>)</li>
<li>[<code>ruff</code>] Add rule to use human-readable names in
configuration selectors (<code>RUF201</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26772">#26772</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>[<code>flake8-pyi</code>] Fix false positive in <code>__all__</code>
(<code>PYI053</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26872">#26872</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>pylint</code>] Ignore mutable type updates in
<code>redefined-loop-name</code> (<code>PLW2901</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25733">#25733</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Avoid redundant lexer token bookkeeping (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26765">#26765</a>)</li>
<li>Avoid redundant pending-indentation writes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26774">#26774</a>)</li>
<li>Avoid unnecessary identifier lookahead (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26525">#26525</a>)</li>
<li>Reuse parser scratch buffers (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26798">#26798</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Document argfile support (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26803">#26803</a>)</li>
<li>[<code>flake8-datetimez</code>] Clarify naming guidance for
<code>datetime.today</code> (<code>DTZ002</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26658">#26658</a>)</li>
<li>[<code>pycodestyle</code>] Document <code>E731</code> fix safety (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26847">#26847</a>)</li>
<li>[<code>ruff</code>] Clarify intentional async contexts for
<code>unused-async</code> (<code>RUF029</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26641">#26641</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/dwego"><code>@​dwego</code></a></li>
<li><a
href="https://github.com/MichaReiser"><code>@​MichaReiser</code></a></li>
<li><a href="https://github.com/Joosboy"><code>@​Joosboy</code></a></li>
<li><a
href="https://github.com/KaufmanDmitriy"><code>@​KaufmanDmitriy</code></a></li>
<li><a
href="https://github.com/PeterJCLaw"><code>@​PeterJCLaw</code></a></li>
<li><a href="https://github.com/ntBre"><code>@​ntBre</code></a></li>
<li><a
href="https://github.com/charliermarsh"><code>@​charliermarsh</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ruff/commit/0177a7e0d2c4a3805afa1960f106d72878766cbc"><code>0177a7e</code></a>
Bump 0.15.22 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26884">#26884</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/fe8ba85a55b76d838e6058d813b64468b5f81eca"><code>fe8ba85</code></a>
[<code>pycodestyle</code>] Document <code>E731</code> fix safety (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26847">#26847</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/f3cf7c81fa35f8b819a719303bf1789714f1c780"><code>f3cf7c8</code></a>
[<code>ruff</code>] Add <code>rule-codes-in-selectors</code>
(<code>RUF201</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26772">#26772</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/d244fd75e9036e61be4896b823fe760d7aa632a3"><code>d244fd7</code></a>
Document argfile support (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26803">#26803</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/f23975849a0db64a2851029c68fc054799d5e7da"><code>f239758</code></a>
[<code>flake8-pyi</code>] Fix false positive in <code>__all__</code>
(<code>PYI053</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26872">#26872</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/54acbcde56d76bc47b0b3dfe61e57fbaadd2c06b"><code>54acbcd</code></a>
[ty] Support <code>TypeVarTuple</code> and <code>Unpack</code> (<a
href="https://redirect.github.com/astral-sh/ruff/issues/25240">#25240</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/87fc38cacd7e2bd906674c6dc5b8be97aa3a5bc3"><code>87fc38c</code></a>
[ty] Parallelize subtype hierarchy search (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26875">#26875</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/e454f91fa63bd745d874ead751ad15d9f521195a"><code>e454f91</code></a>
[ty] Share interned query keys across Salsa queries (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26794">#26794</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/6f361a74a232ad283cc6e5e5d3a62109802aab65"><code>6f361a7</code></a>
[ty] only collect expected types for files open in the editor (<a
href="https://redirect.github.com/astral-sh/ruff/issues/25546">#25546</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/6d6b2b37686da8b1ee56e54fd99831f1f5fa0381"><code>6d6b2b3</code></a>
[ty] Avoid allocations during Salsa interned lookups (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26877">#26877</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ruff/compare/0.15.21...0.15.22">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-21 15:40:08 -04:00
dependabot[bot]andCaliBrain 28969ac828 Bump the npm-deps group across 1 directory with 7 updates (#1112)
Bumps the npm-deps group with 7 updates in the /src/frontend directory:

| Package | From | To |
| --- | --- | --- |
|
[@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)
| `26.0.1` | `26.1.1` |
| [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) |
`6.23.0` | `6.26.0` |
| [oxfmt](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt) |
`0.57.0` | `0.58.0` |
| [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) |
`1.72.0` | `1.73.0` |
| [oxlint-tsgolint](https://github.com/oxc-project/tsgolint) | `0.23.0`
| `0.24.0` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) |
`8.1.0` | `8.1.4` |
|
[vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)
| `4.1.9` | `4.1.10` |


Updates `@types/node` from 26.0.1 to 26.1.1
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node">compare
view</a></li>
</ul>
</details>
<br />

Updates `knip` from 6.23.0 to 6.26.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/webpro-nl/knip/releases">knip's
releases</a>.</em></p>
<blockquote>
<h2>Release 6.26.0</h2>
<ul>
<li>ci: add path filters (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1871">#1871</a>)
(4249935adffe1b8eca9570fb325fa19cc8010584) - thanks <a
href="https://github.com/trueberryless"><code>@​trueberryless</code></a>!</li>
<li>Add CodeRabbit as gold sponsor
(1da09fdc8f4d851f3cad30659200018bfb47a5e0)</li>
<li>Fix up docs a bit more
(39125a7f473e006f61629781bff1ac4a050ca460)</li>
<li>Don't report ambient declaration files as unused
(aed361c00a82829c2fc80a65a2f7af174316782e)</li>
<li>Register oclif command files as entries
(3b4d58c9da83d93b6821a2b24f57b919ba886756)</li>
<li>Add electron-vite plugin
(d92107ea504833b3ee7ad2b695e22d40528b24c3)</li>
<li>Add esbuild plugin (ef3b601d957546554de2cb1fbff859f5bdafb741)</li>
<li>Ignore more globally available binaries
(829298129fdfd1b41a1f8dff5022b9fbbe25f454)</li>
<li>Resolve <code>#</code>-imports to source when node condition is
unbuilt (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1873">#1873</a>)
(f2713ed1e499ab286b86615d4b8ec34f711d1bd2)</li>
<li>Ignore gh as a globally available binary
(a6f0772e5db03a8412a3ac036756779f22d1a27d)</li>
<li>Resolve Vitest benchmark files as entries
(57429139f7e93f2a4bee35a53d450c4f45dea9ff)</li>
<li>Extract shared Vue auto-import machinery into plugins/_vue
(73010753d432ae58eb12c7eec9c5723036009749)</li>
<li>Scope compiler extensions per workspace
(5e6f82b963a83e5801484efbc01a4b6a9ce39ea3)</li>
<li>Resolve auto-imported components in the Vue SFC compiler
(009aad8f9dab589898e5dba4c85fa41ea57c1a49)</li>
<li>Add plugins for the Vue auto-import ecosystem
(f638c8302cdd26a63e477344a7cf44ef60af7788)</li>
<li>Enable Vue SFC compiler on unplugin-vue and
<code>@​vitejs/plugin-vue</code>
(9396ab159919147ef81dad16e365e531b32b4b12)</li>
<li>Recognize vite-plugin-vue-meta-layouts (same layouts convention)
(3ceee89228b0173eddc0466bf50e8181286ffebc)</li>
<li>Add vite-plugin-pages and unplugin-icons plugins
(9bc17540bcf8eb142474ed84837c1e2cebb7fe73)</li>
<li>Add vite-plugin-pwa and <code>@​intlify/unplugin-vue-i18n</code>
plugins (45dea0a397ed754c34617ed18deef4dc6f0ec0d4)</li>
<li>Dog, food. (e1249ade2558d3801d85e2dc63349d884f3443d4)</li>
<li>Update query snapshot
(a45941d2db381c04f8a38416533217ee84f77ca7)</li>
<li>Don't misread Nitro route types as Vue component auto-imports
(43aecd5414eb63bec3637f37ffac0c62a71e7e74)</li>
<li>Read oxlint jsPlugins from vite-plus vite.config and .oxlintrc.json
(589ffda4e3a7d0c0d772191ad6f8d6e60dd298cf)</li>
<li>Add vite-plus plugin for run.tasks and staged scripts
(f041c191e4e10dd181ad212b71079e3e10bcf341)</li>
<li>Support <code>@​vite-pwa/nuxt</code> PWA config in nuxt.config
(8fa7b116690e37484798ad98eca002d26c0c80fe)</li>
<li>Add <code>@​vite-pwa/assets-generator</code> plugin
(4254f7d53a284a4ea4612f650ae03495bcb0d582)</li>
<li>Add <code>@​nuxtjs/i18n</code> plugin
(dfb9acbe5a08ebc85562f59a04e7f4bedb4925f1)</li>
<li>Read plugin entries from vite.config options and index.html
(d533da8cc2b431d206eb7c55323ea51846eca0bb)</li>
<li>Fix false positives in VitePress, next-mdx and unplugin-vue-i18n
plugins (b99702a76e26b1943a16517f1633de3a0713c196)</li>
<li>Respect optional peers in pnpm and Yarn packageExtensions
(aab080bbd436494316df045011ab22cbebca06b4)</li>
<li>Detect babel plugins in the Storybook config
(5dea975adf9b88971ad56434132282a099c7d58c)</li>
<li>Add shared AST helpers for imported calls and first property values
(c84bb7a87a17f1921c12bb02bcfd43f905cae595)</li>
<li>Inline trivial resolveFromAST wrappers and normalize orval and sst
(620079d1d07b69a69bbb67b6a6e68963b6c2fc21)</li>
<li>Extract inline resolveFromAST implementations to dedicated files
(b5231c12f915428067d790187d2dd4c44e2559d4)</li>
<li>Normalize resolveFromAST files to a uniform contract
(fc1ba0fb45aaf623de678a57bea3b5567ea04750)</li>
<li>chore: migrate to Astro 7 and Sätteri (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1875">#1875</a>)
(f256a5b276c3d69dd5081c591bbb183aacebf766) - thanks <a
href="https://github.com/trueberryless"><code>@​trueberryless</code></a>!</li>
<li>Show contributor count on docs homepage
(6b8454a2841905ce7f08e1a23b54bc17a0854608)</li>
<li>Exclude gitignored package entry points
(606e5d051b4f0620c776aa23352153b8253bd75b)</li>
<li>Add Tauri plugin (199180da79c71acc80865dc65ce89653a26d9579)</li>
<li>Add Laravel plugin (197453378f2b60e5143a30ec78b438f02b37c53b)</li>
<li>Add Quasar plugin (a220729074bfbb9e7d03ba329e89ad553c3a7799)</li>
</ul>
<h2>Release 6.25.0</h2>
<ul>
<li>fix: derive package name from package.json under Yarn
<code>nodeLinker: pnpm</code> store (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1852">#1852</a>)
(3764605ed150b975fb6c88c4c4aa47f5bf9ae149) - thanks <a
href="https://github.com/blowery"><code>@​blowery</code></a>!</li>
<li>Pick up types used in module augmentations via path aliases (resolve
<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1850">#1850</a>)
(9cc5a4132793990b5fcf9eae6f3db37426d979c0)</li>
<li>feat: add support for <code>packageExtensions</code> in yarn and
pnpm (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1847">#1847</a>)
(61d3164cdac6e9252b3c52ce3bb2909949b4690e) - thanks <a
href="https://github.com/trueberryless"><code>@​trueberryless</code></a>!</li>
<li>fix(ci): do not run integration on forks (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1855">#1855</a>)
(d23c10d1ae764c0b70addaafc79f01c543cc5efa) - thanks <a
href="https://github.com/trueberryless"><code>@​trueberryless</code></a>!</li>
<li>chore(ci): improve security through specific permissions (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1856">#1856</a>)
(1eb42e639f9ec12d6d2ef88e1aaf07397b5bf226) - thanks <a
href="https://github.com/trueberryless"><code>@​trueberryless</code></a>!</li>
<li>chore(ci): add concurrency blocks (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1857">#1857</a>)
(3bf3d115cf677fadcfe8fa72ce0e68cfd435d399) - thanks <a
href="https://github.com/trueberryless"><code>@​trueberryless</code></a>!</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/webpro-nl/knip/commit/441faf08dfa1e87daa342620026947c99a98ec40"><code>441faf0</code></a>
Release knip@6.26.0</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/a220729074bfbb9e7d03ba329e89ad553c3a7799"><code>a220729</code></a>
Add Quasar plugin</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/197453378f2b60e5143a30ec78b438f02b37c53b"><code>1974533</code></a>
Add Laravel plugin</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/199180da79c71acc80865dc65ce89653a26d9579"><code>199180d</code></a>
Add Tauri plugin</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/606e5d051b4f0620c776aa23352153b8253bd75b"><code>606e5d0</code></a>
Exclude gitignored package entry points</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/fc1ba0fb45aaf623de678a57bea3b5567ea04750"><code>fc1ba0f</code></a>
Normalize resolveFromAST files to a uniform contract</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/b5231c12f915428067d790187d2dd4c44e2559d4"><code>b5231c1</code></a>
Extract inline resolveFromAST implementations to dedicated files</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/620079d1d07b69a69bbb67b6a6e68963b6c2fc21"><code>620079d</code></a>
Inline trivial resolveFromAST wrappers and normalize orval and sst</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/c84bb7a87a17f1921c12bb02bcfd43f905cae595"><code>c84bb7a</code></a>
Add shared AST helpers for imported calls and first property values</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/5dea975adf9b88971ad56434132282a099c7d58c"><code>5dea975</code></a>
Detect babel plugins in the Storybook config</li>
<li>Additional commits viewable in <a
href="https://github.com/webpro-nl/knip/commits/knip@6.26.0/packages/knip">compare
view</a></li>
</ul>
</details>
<br />

Updates `oxfmt` from 0.57.0 to 0.58.0
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/commit/39677ba50d908ea09f6d9e58ded328461212f52a"><code>39677ba</code></a>
release(apps): oxlint v1.73.0 &amp;&amp; oxfmt v0.58.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/24219">#24219</a>)</li>
<li>See full diff in <a
href="https://github.com/oxc-project/oxc/commits/oxfmt_v0.58.0/npm/oxfmt">compare
view</a></li>
</ul>
</details>
<br />

Updates `oxlint` from 1.72.0 to 1.73.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/releases">oxlint's
releases</a>.</em></p>
<blockquote>
<h2>oxlint v1.27.0 &amp;&amp; oxfmt v0.12.0</h2>
<h1>Oxlint v1.27.0</h1>
<h3>🚀 Features</h3>
<ul>
<li>222a8f0 linter/plugins: Implement
<code>SourceCode#isSpaceBetween</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15498">#15498</a>)
(overlookmotel)</li>
<li>2f9735d linter/plugins: Implement
<code>context.languageOptions</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15486">#15486</a>)
(overlookmotel)</li>
<li>bc731ff linter/plugins: Stub out all <code>Context</code> APIs (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15479">#15479</a>)
(overlookmotel)</li>
<li>5822cb4 linter/plugins: Add <code>extend</code> method to
<code>FILE_CONTEXT</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15477">#15477</a>)
(overlookmotel)</li>
<li>7b1e6f3 apps: Add pure rust binaries and release to github (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15469">#15469</a>)
(Boshen)</li>
<li>2a89b43 linter: Introduce debug assertions after fixes to assert
validity (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15389">#15389</a>)
(camc314)</li>
<li>ad3c45a editor: Add <code>oxc.path.node</code> option (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15040">#15040</a>)
(Sysix)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>6f3cd77 linter/no-var: Incorrect warning for blocks (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15504">#15504</a>)
(Hamir Mahal)</li>
<li>6957fb9 linter/plugins: Do not allow access to
<code>Context#id</code> in <code>createOnce</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15489">#15489</a>)
(overlookmotel)</li>
<li>7409630 linter/plugins: Allow access to <code>cwd</code> in
<code>createOnce</code> in ESLint interop mode (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15488">#15488</a>)
(overlookmotel)</li>
<li>732205e parser: Reject <code>using</code> / <code>await using</code>
in a switch <code>case</code> / <code>default</code> clause (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15225">#15225</a>)
(sapphi-red)</li>
<li>a17ca32 linter/plugins: Replace <code>Context</code> class (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15448">#15448</a>)
(overlookmotel)</li>
<li>ecf2f7b language_server: Fail gracefully when tsgolint executable
not found (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15436">#15436</a>)
(camc314)</li>
<li>3c8d3a7 lang-server: Improve logging in failure case for tsgolint
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15299">#15299</a>)
(camc314)</li>
<li>ef71410 linter: Use jsx if source type is JS in fix debug assertion
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15434">#15434</a>)
(camc314)</li>
<li>e32bbf6 linter/no-var: Handle TypeScript declare keyword in fixer
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15426">#15426</a>)
(camc314)</li>
<li>6565dbe linter/switch-case-braces: Skip comments when searching for
<code>:</code> token (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15425">#15425</a>)
(camc314)</li>
<li>85bd19a linter/prefer-class-fields: Insert value after type
annotation in fixer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15423">#15423</a>)
(camc314)</li>
<li>fde753e linter/plugins: Block access to
<code>context.settings</code> in <code>createOnce</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15394">#15394</a>)
(overlookmotel)</li>
<li>ddd9f9f linter/forward-ref-uses-ref: Dont suggest removing wrapper
in invalid positions (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15388">#15388</a>)
(camc314)</li>
<li>dac2a9c linter/no-template-curly-in-string: Remove fixer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15387">#15387</a>)
(camc314)</li>
<li>989b8e3 linter/no-var: Only fix to <code>const</code> if the var has
an initializer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15385">#15385</a>)
(camc314)</li>
<li>cc403f5 linter/plugins: Return empty object for unimplemented
parserServices (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15364">#15364</a>)
(magic-akari)</li>
</ul>
<h3>⚡ Performance</h3>
<ul>
<li>25d577e language_server: Start tools in parallel (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15500">#15500</a>)
(Sysix)</li>
<li>3c57291 linter/plugins: Optimize loops (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15449">#15449</a>)
(overlookmotel)</li>
<li>3166233 linter/plugins: Remove <code>Arc</code>s (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15431">#15431</a>)
(overlookmotel)</li>
<li>9de1322 linter/plugins: Lazily deserialize settings JSON (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15395">#15395</a>)
(overlookmotel)</li>
<li>3049ec2 linter/plugins: Optimize <code>deepFreezeSettings</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15392">#15392</a>)
(overlookmotel)</li>
<li>444ebfd linter/plugins: Use single object for
<code>parserServices</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15378">#15378</a>)
(overlookmotel)</li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>97d2104 linter: Update comment in lint.rs about default value for
tsconfig path (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15530">#15530</a>)
(Connor Shea)</li>
<li>2c6bd9e linter: Always refer as &quot;ES2015&quot; instead of
&quot;ES6&quot; (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15411">#15411</a>)
(sapphi-red)</li>
<li>a0c5203 linter/import/named: Update &quot;ES7&quot; comment in
examples (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15410">#15410</a>)
(sapphi-red)</li>
<li>3dc24b5 linter,minifier: Always refer as &quot;ES Modules&quot;
instead of &quot;ES6 Modules&quot; (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15409">#15409</a>)
(sapphi-red)</li>
<li>2ad77fb linter/no-this-before-super: Correct &quot;Why is this
bad?&quot; section (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15408">#15408</a>)
(sapphi-red)</li>
<li>57f0ce1 linter: Add backquotes where appropriate (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15407">#15407</a>)
(sapphi-red)</li>
</ul>
<h1>Oxfmt v0.12.0</h1>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md">oxlint's
changelog</a>.</em></p>
<blockquote>
<h2>[1.73.0] - 2026-07-06</h2>
<h3>🚀 Features</h3>
<ul>
<li>a2c97f3 linter/unicorn: Implement <code>explicit-timer-delay</code>
rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23612">#23612</a>)
(Mikhail Baev)</li>
<li>85735cb linter/unicorn: Implement
<code>no-confusing-array-with</code> rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23638">#23638</a>)
(Shekhu☺️)</li>
<li>cb4fbb9 linter/eslint: Implement no-unreachable-loop rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23975">#23975</a>)
(Todor Andonov)</li>
<li>dc32112 linter/eslint/no-constant-binary-expression: Check
relational comparisons (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24088">#24088</a>)
(camc314)</li>
<li>d963967 linter/unicorn/no-array-sort: Add
<code>allowAfterSpread</code> option (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24043">#24043</a>)
(Boshen)</li>
<li>0a75682 linter: Add per-rule timings for type-aware linting (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/22488">#22488</a>)
(camchenry)</li>
<li>743e222 linter/react: Add <code>disallowedValues</code> option for
<code>forbid-dom-props</code> rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23970">#23970</a>)
(Mikhail Baev)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>bdb51c7 linter/jest/prefer-ending-with-an-expect: Validate config
patterns (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24122">#24122</a>)
(camc314)</li>
<li>45d607d linter/react/forbid-component-props: Make allow/disallow
lists optional in schema (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24024">#24024</a>)
(Boshen)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/commit/39677ba50d908ea09f6d9e58ded328461212f52a"><code>39677ba</code></a>
release(apps): oxlint v1.73.0 &amp;&amp; oxfmt v0.58.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24219">#24219</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/a2c97f348c8bdabe99a08b2fbb45d32ffe49a529"><code>a2c97f3</code></a>
feat(linter/unicorn): implement <code>explicit-timer-delay</code> rule
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23612">#23612</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/bdb51c73da9edeef02ce39477167765d971fb5cb"><code>bdb51c7</code></a>
fix(linter/jest/prefer-ending-with-an-expect): validate config patterns
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24122">#24122</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/85735cb5df27daffd32651f808a382680ee92aaf"><code>85735cb</code></a>
feat(linter/unicorn): implement <code>no-confusing-array-with</code>
rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23638">#23638</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/cb4fbb927f237b0ba4937a8de5a7b28c444b4114"><code>cb4fbb9</code></a>
feat(linter/eslint): implement no-unreachable-loop rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23975">#23975</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/dc321127faa1e0f7bff293319717c52f9c8f6b95"><code>dc32112</code></a>
feat(linter/eslint/no-constant-binary-expression): check relational
compariso...</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/d96396707923bcffa92e732b15bf23631561f35c"><code>d963967</code></a>
feat(linter/unicorn/no-array-sort): add <code>allowAfterSpread</code>
option (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/24043">#24043</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/45d607d48529dc9f32f53436ee816fc02494c64b"><code>45d607d</code></a>
fix(linter/react/forbid-component-props): make allow/disallow lists
optional ...</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/0a75682fcb38014878d53f37075f32e12101e265"><code>0a75682</code></a>
feat(linter): add per-rule timings for type-aware linting (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/22488">#22488</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/743e22293bc5317c79b6b083ffbb2bd5ab5ebefc"><code>743e222</code></a>
feat(linter/react): add <code>disallowedValues</code> option for
<code>forbid-dom-props</code> rule...</li>
<li>See full diff in <a
href="https://github.com/oxc-project/oxc/commits/oxlint_v1.73.0/npm/oxlint">compare
view</a></li>
</ul>
</details>
<br />

Updates `oxlint-tsgolint` from 0.23.0 to 0.24.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/tsgolint/releases">oxlint-tsgolint's
releases</a>.</em></p>
<blockquote>
<h2>v0.24.0</h2>
<h2>What's Changed</h2>
<ul>
<li>ci: run windows e2e pnpm commands outside workspace by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/969">oxc-project/tsgolint#969</a></li>
<li>test(no-deprecated): add test for only <code>allow</code> by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/970">oxc-project/tsgolint#970</a></li>
<li>fix(no-deprecated): report contextual object literal properties by
<a href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/944">oxc-project/tsgolint#944</a></li>
<li>Update README with PRs note on new rules by <a
href="https://github.com/camchenry"><code>@​camchenry</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/972">oxc-project/tsgolint#972</a></li>
<li>fix(strict-boolean-expressions): guard invalid predicate indexes by
<a href="https://github.com/camchenry"><code>@​camchenry</code></a> in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/971">oxc-project/tsgolint#971</a></li>
<li>fix(no-deprecated): fix crash when getting prop name for non-literal
by <a href="https://github.com/camchenry"><code>@​camchenry</code></a>
in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/973">oxc-project/tsgolint#973</a></li>
<li>test(no-deprecated): add non-literal computed key regression by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/974">oxc-project/tsgolint#974</a></li>
<li>test(no-floating-promises): sync tests with typescript-eslint by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/975">oxc-project/tsgolint#975</a></li>
<li>fix(no-floating-promises): handle spread arguments before rejection
handlers by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/976">oxc-project/tsgolint#976</a></li>
<li>chore(deps): update crate-ci/typos action to v1.46.2 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/979">oxc-project/tsgolint#979</a></li>
<li>refactor: remove unneeded <code>nil</code> checks in
<code>strict-boolean-expressions</code> by <a
href="https://github.com/camchenry"><code>@​camchenry</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/980">oxc-project/tsgolint#980</a></li>
<li>perf(no-unsafe-assignment): add fast-path for sender type by <a
href="https://github.com/camchenry"><code>@​camchenry</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/981">oxc-project/tsgolint#981</a></li>
<li>feat: add per-rule debug timings by <a
href="https://github.com/camchenry"><code>@​camchenry</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/960">oxc-project/tsgolint#960</a></li>
<li>chore: update typescript-go submodule by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/982">oxc-project/tsgolint#982</a></li>
<li>chore(deps): update dprint plugins by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/984">oxc-project/tsgolint#984</a></li>
<li>chore(deps): update github actions by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/985">oxc-project/tsgolint#985</a></li>
<li>chore(deps): update gomod by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/986">oxc-project/tsgolint#986</a></li>
<li>chore(deps): update crate-ci/typos action to v1.46.3 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/988">oxc-project/tsgolint#988</a></li>
<li>test(no-deprecated): cover call and jsx deprecated properties by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/989">oxc-project/tsgolint#989</a></li>
<li>chore: update typescript-go submodule by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/991">oxc-project/tsgolint#991</a></li>
<li>chore(deps): update github actions by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/993">oxc-project/tsgolint#993</a></li>
<li>chore(deps): update crate-ci/typos action to v1.47.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/995">oxc-project/tsgolint#995</a></li>
<li>chore(deps): update module github.com/dlclark/regexp2/v2 to v2.1.1
by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/994">oxc-project/tsgolint#994</a></li>
<li>fix(prefer-optional-chain): suggest unsafe nullish rewrite by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/996">oxc-project/tsgolint#996</a></li>
<li>chore(deps): update pnpm to v11.4.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/997">oxc-project/tsgolint#997</a></li>
<li>refactor: cache suppress program diagnostics env by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/999">oxc-project/tsgolint#999</a></li>
<li>refactor: use <code>sync.OnceValue</code> for logLevel by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1000">oxc-project/tsgolint#1000</a></li>
<li>test(no-unnecessary-type-assertion): sync tests with
typescript-eslint by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/998">oxc-project/tsgolint#998</a></li>
<li>fix(no-unnecessary-type-assertion): enable skipped upstream cases by
<a href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1001">oxc-project/tsgolint#1001</a></li>
<li>fix(justfile): use --ignore-workspace for e2e pnpm commands by <a
href="https://github.com/swandir"><code>@​swandir</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/983">oxc-project/tsgolint#983</a></li>
<li>test(no-unnecessary-type-arguments): sync tests with
typescript-eslint by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1003">oxc-project/tsgolint#1003</a></li>
<li>test(no-unnecessary-type-assertion): cover nullable literal
assertion by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1002">oxc-project/tsgolint#1002</a></li>
<li>fix(no-unnecessary-type-arguments): preserve constructor inference
by <a href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1004">oxc-project/tsgolint#1004</a></li>
<li>refactor: remove <code>OXLINT_TSGOLINT_TIMINGS</code> env var by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1005">oxc-project/tsgolint#1005</a></li>
<li>chore: update typescript-go submodule by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1007">oxc-project/tsgolint#1007</a></li>
<li>fix(prefer-optional-chain): optional chain type assertion handling
by <a href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1009">oxc-project/tsgolint#1009</a></li>
<li>chore(deps): update typos to v1.47.1 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1010">oxc-project/tsgolint#1010</a></li>
<li>chore(deps): update typos to v1.47.2 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1012">oxc-project/tsgolint#1012</a></li>
<li>chore(deps): update github actions by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1013">oxc-project/tsgolint#1013</a></li>
<li>chore(deps): update gomod by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1014">oxc-project/tsgolint#1014</a></li>
<li>chore(deps): update github actions to v2.81.5 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1015">oxc-project/tsgolint#1015</a></li>
<li>chore: update typescript-go submodule by <a
href="https://github.com/camc314"><code>@​camc314</code></a> in <a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1016">oxc-project/tsgolint#1016</a></li>
<li>chore(deps): update github actions by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1017">oxc-project/tsgolint#1017</a></li>
<li>chore(deps): update gomod to v0.46.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1018">oxc-project/tsgolint#1018</a></li>
<li>chore(deps): update gomod to v2.2.2 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1021">oxc-project/tsgolint#1021</a></li>
<li>chore(deps): update github actions to v2.82.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1023">oxc-project/tsgolint#1023</a></li>
<li>chore(deps): update dprint plugins to v0.22.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1022">oxc-project/tsgolint#1022</a></li>
<li>chore(deps): update github actions to v7 by <a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot] in
<a
href="https://redirect.github.com/oxc-project/tsgolint/pull/1024">oxc-project/tsgolint#1024</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/5a37e8902f65440900be1436b814919fcdb4e3d4"><code>5a37e89</code></a>
fix(dot-notation): determine the relevant accessor (<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1028">#1028</a>)</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/67a281f613312a30665e7d39dfb47f5c15b0ca60"><code>67a281f</code></a>
perf(consistent-return): defer per-function type resolution (<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1031">#1031</a>)</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/a5e2ff0be1102d9082e13ec35f1f0e4ca7fa65ad"><code>a5e2ff0</code></a>
perf(no-unnecessary-qualifier): skip symbol resolution outside
namespaces. (#...</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/a8fc6685df0a817e7c5045a4e3e141d342839959"><code>a8fc668</code></a>
perf(no-confusing-void-expression): check ancestor position before type
query...</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/03158cc4c0a0ef2c8bdccab71e6d2723fcc1c4f2"><code>03158cc</code></a>
perf(no-unnecessary-type-conversion): hoist constant builtin-name slices
(<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1040">#1040</a>)</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/d9e645c266b6c4b4671b0a3dc5d3935dbda3a5ef"><code>d9e645c</code></a>
perf(prefer-optional-chain): lazily allocate chain-processor caches (<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1041">#1041</a>)</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/63f578abe093d7a7e8cb523a1e5e96aacf06b220"><code>63f578a</code></a>
refactor(no-unnecessary-condition): remove dead
containsUnguardedElementAcces...</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/f174876a906e0350aff2b75d5607775529c9f845"><code>f174876</code></a>
chore(deps): update gomod (<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1035">#1035</a>)</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/47de9cfea53da0ae274bc78d47388e3bdd5b7341"><code>47de9cf</code></a>
chore(deps): update github actions (<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1036">#1036</a>)</li>
<li><a
href="https://github.com/oxc-project/tsgolint/commit/e209b5b27f5fd22810735bfbfdb0785cbc5d4215"><code>e209b5b</code></a>
chore(deps): update actions/cache action to v6 (<a
href="https://redirect.github.com/oxc-project/tsgolint/issues/1037">#1037</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/oxc-project/tsgolint/compare/v0.23.0...v0.24.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `vite` from 8.1.0 to 8.1.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/releases">vite's
releases</a>.</em></p>
<blockquote>
<h2>v8.1.4</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v8.1.4/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v8.1.3</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v8.1.3/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v8.1.2</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v8.1.2/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v8.1.1</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v8.1.1/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md">vite's
changelog</a>.</em></p>
<blockquote>
<h2><!-- raw HTML omitted --><a
href="https://github.com/vitejs/vite/compare/v8.1.3...v8.1.4">8.1.4</a>
(2026-07-09)<!-- raw HTML omitted --></h2>
<h3>Features</h3>
<ul>
<li><strong>legacy:</strong> prefer oxc as minifier (fix <a
href="https://redirect.github.com/vitejs/vite/issues/21973">#21973</a>)
(<a
href="https://redirect.github.com/vitejs/vite/issues/22468">#22468</a>)
(<a
href="https://github.com/vitejs/vite/commit/ab5dafa8e66296ef201f615489fb57954bb740ce">ab5dafa</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>build:</strong> add workaround for building on stackblitz
(<a
href="https://redirect.github.com/vitejs/vite/issues/22840">#22840</a>)
(<a
href="https://github.com/vitejs/vite/commit/575c32c29925c554f4ef4068738ab89c6878f615">575c32c</a>)</li>
<li><strong>build:</strong> keep <code>import.meta.url</code> in preload
function as-is (<a
href="https://redirect.github.com/vitejs/vite/issues/22839">#22839</a>)
(<a
href="https://github.com/vitejs/vite/commit/f1f90ed4742b3cf453428c7e581a6016a4d47321">f1f90ed</a>)</li>
<li><strong>deps:</strong> update all non-major dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/22865">#22865</a>)
(<a
href="https://github.com/vitejs/vite/commit/d4295a9ffce428c0e51892373e00c07fccc0498a">d4295a9</a>)</li>
<li><strong>deps:</strong> update rolldown-related dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/22866">#22866</a>)
(<a
href="https://github.com/vitejs/vite/commit/7cf07e4c5f7a6af276012e0c2ec06e08499e951e">7cf07e4</a>)</li>
<li><strong>html:</strong> avoid backtracking in import-only check (<a
href="https://redirect.github.com/vitejs/vite/issues/22848">#22848</a>)
(<a
href="https://github.com/vitejs/vite/commit/b5868c01a124d345664450aaebd677bfba964c05">b5868c0</a>)</li>
<li><strong>optimizer:</strong> avoid optimizer run for transform
request before init (<a
href="https://redirect.github.com/vitejs/vite/issues/22852">#22852</a>)
(<a
href="https://github.com/vitejs/vite/commit/72a5e2192506f51a5efd09c059815a5c43a9eacb">72a5e21</a>)</li>
<li><strong>ssr:</strong> align named export function call stacktrace
column with Node (<a
href="https://redirect.github.com/vitejs/vite/issues/22829">#22829</a>)
(<a
href="https://github.com/vitejs/vite/commit/173a1b648c321e0f836e5a94fc47c4fa9b081bfc">173a1b6</a>)</li>
<li>strip pure CSS chunk imports when chunkImportMap is enabled (<a
href="https://redirect.github.com/vitejs/vite/issues/22841">#22841</a>)
(<a
href="https://github.com/vitejs/vite/commit/648bd04933093d0aac9565f21a49811437776886">648bd04</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>fix incorrect <code>@default</code> for <code>server.cors</code> (<a
href="https://redirect.github.com/vitejs/vite/issues/22859">#22859</a>)
(<a
href="https://github.com/vitejs/vite/commit/70435b2551ee4fe3a0d55c8a3bb61b96f44d2763">70435b2</a>)</li>
</ul>
<h3>Miscellaneous Chores</h3>
<ul>
<li><strong>deps:</strong> update dependency postcss-modules to v9 (<a
href="https://redirect.github.com/vitejs/vite/issues/22867">#22867</a>)
(<a
href="https://github.com/vitejs/vite/commit/a9539d69efc7a4ccf988bbf5da31c2b416ba990e">a9539d6</a>)</li>
</ul>
<h3>Code Refactoring</h3>
<ul>
<li>eliminate ineffectiveDynamicImport warn (<a
href="https://redirect.github.com/vitejs/vite/issues/22876">#22876</a>)
(<a
href="https://github.com/vitejs/vite/commit/ea22fb352aad9a42c0a9d08f39d8a0bae8c111a5">ea22fb3</a>)</li>
</ul>
<h3>Tests</h3>
<ul>
<li>avoid warnings (<a
href="https://redirect.github.com/vitejs/vite/issues/22851">#22851</a>)
(<a
href="https://github.com/vitejs/vite/commit/af21ab68adac3380dc9a854d2fe3f776654301cd">af21ab6</a>)</li>
</ul>
<h3>Build System</h3>
<ul>
<li>remove the custom onLog function (<a
href="https://redirect.github.com/vitejs/vite/issues/22878">#22878</a>)
(<a
href="https://github.com/vitejs/vite/commit/2c4a217a63dc0a7fcb10bc710a988f32b173f481">2c4a217</a>)</li>
<li>replace deprecated <code>onwarn</code> with <code>onLog</code> (<a
href="https://redirect.github.com/vitejs/vite/issues/22741">#22741</a>)
(<a
href="https://github.com/vitejs/vite/commit/c581b5588cb8e94603d17bce2ff9fec8e8e0a3bf">c581b55</a>)</li>
</ul>
<h2><!-- raw HTML omitted --><a
href="https://github.com/vitejs/vite/compare/v8.1.2...v8.1.3">8.1.3</a>
(2026-07-02)<!-- raw HTML omitted --></h2>
<h3>Bug Fixes</h3>
<ul>
<li><strong>css:</strong> inject inlined CSS after the shebang line (<a
href="https://redirect.github.com/vitejs/vite/issues/22717">#22717</a>)
(<a
href="https://github.com/vitejs/vite/commit/1534d362794e856cc718b123cb245e0efcf36b9a">1534d36</a>)</li>
<li><strong>deps:</strong> bump <code>es-module-lexer</code> to 2.3.0
(<a
href="https://redirect.github.com/vitejs/vite/issues/22838">#22838</a>)
(<a
href="https://github.com/vitejs/vite/commit/7103c3af795396e2ad87e1125eb3fc20d60d9430">7103c3a</a>)</li>
<li>preload css for nested dynamic imports (<a
href="https://redirect.github.com/vitejs/vite/issues/22759">#22759</a>)
(<a
href="https://github.com/vitejs/vite/commit/2c530542da1c50b95b7f0cb17adf82f64ecf97f3">2c53054</a>)</li>
<li><strong>ssr:</strong> correct stacktrace column position for first
line (<a
href="https://redirect.github.com/vitejs/vite/issues/22828">#22828</a>)
(<a
href="https://github.com/vitejs/vite/commit/c4acd6982a051fa10dbd7e3a37e00f0c467793b8">c4acd69</a>)</li>
</ul>
<h2><!-- raw HTML omitted --><a
href="https://github.com/vitejs/vite/compare/v8.1.1...v8.1.2">8.1.2</a>
(2026-06-30)<!-- raw HTML omitted --></h2>
<h3>Bug Fixes</h3>
<ul>
<li><strong>deps:</strong> revert es-module-lexer to 2.1.0 (<a
href="https://redirect.github.com/vitejs/vite/issues/22827">#22827</a>)
(<a
href="https://github.com/vitejs/vite/commit/0d3bd7c4111b8345cfc353c7bbb2c30601752e26">0d3bd7c</a>)</li>
<li>restore, &quot;fix: resolve pnpm .modules.yaml from workspace root
instead of cwd (<a
href="https://redirect.github.com/vitejs/vite/issues/22757">#22757</a>)&quot;
(<a
href="https://redirect.github.com/vitejs/vite/issues/22825">#22825</a>)
(<a
href="https://github.com/vitejs/vite/commit/efb98cce73f3f89c4f03936ab7741cd5b0636f5c">efb98cc</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/vitejs/vite/commit/a477454442eff649b430f9e3c6caf2500fcb7183"><code>a477454</code></a>
release: v8.1.4</li>
<li><a
href="https://github.com/vitejs/vite/commit/ab5dafa8e66296ef201f615489fb57954bb740ce"><code>ab5dafa</code></a>
feat(legacy): prefer oxc as minifier (fix <a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21973">#21973</a>)
(<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22468">#22468</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/173a1b648c321e0f836e5a94fc47c4fa9b081bfc"><code>173a1b6</code></a>
fix(ssr): align named export function call stacktrace column with Node
(<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22829">#22829</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/575c32c29925c554f4ef4068738ab89c6878f615"><code>575c32c</code></a>
fix(build): add workaround for building on stackblitz (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22840">#22840</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/72a5e2192506f51a5efd09c059815a5c43a9eacb"><code>72a5e21</code></a>
fix(optimizer): avoid optimizer run for transform request before init
(<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22852">#22852</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/a9539d69efc7a4ccf988bbf5da31c2b416ba990e"><code>a9539d6</code></a>
chore(deps): update dependency postcss-modules to v9 (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22867">#22867</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/70435b2551ee4fe3a0d55c8a3bb61b96f44d2763"><code>70435b2</code></a>
docs: fix incorrect <code>@default</code> for <code>server.cors</code>
(<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22859">#22859</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/2c4a217a63dc0a7fcb10bc710a988f32b173f481"><code>2c4a217</code></a>
build: remove the custom onLog function (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22878">#22878</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/c581b5588cb8e94603d17bce2ff9fec8e8e0a3bf"><code>c581b55</code></a>
build: replace deprecated <code>onwarn</code> with <code>onLog</code>
(<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22741">#22741</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/ea22fb352aad9a42c0a9d08f39d8a0bae8c111a5"><code>ea22fb3</code></a>
refactor: eliminate ineffectiveDynamicImport warn (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22876">#22876</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/vitejs/vite/commits/v8.1.4/packages/vite">compare
view</a></li>
</ul>
</details>
<br />

Updates `vitest` from 4.1.9 to 4.1.10
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitest-dev/vitest/releases">vitest's
releases</a>.</em></p>
<blockquote>
<h2>v4.1.10</h2>
<h3>   🐞 Bug Fixes</h3>
<ul>
<li><strong>browser</strong>: Check fs access in builtin commands
[backport to v4]  -  by <a
href="https://github.com/hi-ogawa"><code>@​hi-ogawa</code></a>,
<strong>Hiroshi Ogawa</strong> and <strong>OpenCode
(claude-opus-4-8)</strong> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10680">vitest-dev/vitest#10680</a>
<a href="https://github.com/vitest-dev/vitest/commit/5c18dd267"><!-- raw
HTML omitted -->(5c18d)<!-- raw HTML omitted --></a></li>
<li><strong>vm</strong>: Fix external module resolve error with deps
optimizer query for encoded URI [backport to v4]  -  by <a
href="https://github.com/SveLil"><code>@​SveLil</code></a> and <a
href="https://github.com/hi-ogawa"><code>@​hi-ogawa</code></a> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10661">vitest-dev/vitest#10661</a>
<a href="https://github.com/vitest-dev/vitest/commit/bae52b511"><!-- raw
HTML omitted -->(bae52)<!-- raw HTML omitted --></a></li>
</ul>
<h5>    <a
href="https://github.com/vitest-dev/vitest/compare/v4.1.9...v4.1.10">View
changes on GitHub</a></h5>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/vitest-dev/vitest/commit/db616d227b6e0cb07a94f5d1bba262ee95db7e46"><code>db616d2</code></a>
chore: release v4.1.10 (<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/10718">#10718</a>)</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/bae52b5112a6fd8200101b88bf8af9685d077295"><code>bae52b5</code></a>
fix(vm): fix external module resolve error with deps optimizer query for
enco...</li>
<li>See full diff in <a
href="https://github.com/vitest-dev/vitest/commits/v4.1.10/packages/vitest">compare
view</a></li>
</ul>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: CaliBrain <calibrain@l4n.xyz>
2026-07-19 03:29:15 -04:00
dependabot[bot] 2f5d3dde7d Bump seleniumbase from 4.50.6 to 4.51.0 in the python-deps group (#1118)
Bumps the python-deps group with 1 update:
[seleniumbase](https://github.com/seleniumbase/SeleniumBase).

Updates `seleniumbase` from 4.50.6 to 4.51.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.51.0 - CDP Mode: Patch 119</h2>
<h2>CDP Mode: Patch 119</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/5ca71c54fa8e22004591788a7d76aa77add3fccf">Update
CDP Mode</a>
--&gt; New methods for logging from CDP Mode</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/a1a0cdf6476505a5fb10190196ced67487749879">Don't
ignore the cache by default on reload()</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/97b6bf3fef6d3cda8835a1af730c31e06ee27505">Update
the docs</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/775a91bc1843508b9655af396585de376b558e7b">Update
examples</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/6f38a2469aa9122eeb188ea683f5b39a55753ee5">Refresh
Python dependencies</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>CDP Mode: Patch 119 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4422">seleniumbase/SeleniumBase#4422</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.50.6...v4.51.0">https://github.com/seleniumbase/SeleniumBase/compare/v4.50.6...v4.51.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/36627158b4a395a3793dd97e762e282dc97e2a54"><code>3662715</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4422">#4422</a>
from seleniumbase/cdp-mode-patch-119</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/78abd3effed503e5abfa64fd5697a556229dfe02"><code>78abd3e</code></a>
Version 4.51.0</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/6f38a2469aa9122eeb188ea683f5b39a55753ee5"><code>6f38a24</code></a>
Refresh Python dependencies</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/775a91bc1843508b9655af396585de376b558e7b"><code>775a91b</code></a>
Update examples</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/97b6bf3fef6d3cda8835a1af730c31e06ee27505"><code>97b6bf3</code></a>
Update the docs</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/a1a0cdf6476505a5fb10190196ced67487749879"><code>a1a0cdf</code></a>
Don't ignore the cache by default on reload()</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/5ca71c54fa8e22004591788a7d76aa77add3fccf"><code>5ca71c5</code></a>
Update CDP Mode</li>
<li>See full diff in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.50.6...v4.51.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=seleniumbase&package-manager=uv&previous-version=4.50.6&new-version=4.51.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-19 02:52:13 -04:00
dependabot[bot] 2d949d4e7c Bump python from b877e50 to d3400aa (#1119)
Bumps python from `b877e50` to `d3400aa`.


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=python&package-manager=docker&previous-version=3.14.6-slim&new-version=3.14.6-slim)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-19 02:52:03 -04:00
dependabot[bot] 100e0dd9f8 Bump the gh-actions group across 1 directory with 10 updates (#1120)
Bumps the gh-actions group with 10 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [docker/login-action](https://github.com/docker/login-action) |
`4.2.0` | `4.4.0` |
| [docker/metadata-action](https://github.com/docker/metadata-action) |
`6.1.0` | `6.2.0` |
|
[docker/setup-buildx-action](https://github.com/docker/setup-buildx-action)
| `4.1.0` | `4.2.0` |
|
[docker/build-push-action](https://github.com/docker/build-push-action)
| `7.2.0` | `7.3.0` |
| [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) | `8.2.0`
| `8.3.2` |
| [actions/setup-node](https://github.com/actions/setup-node) | `6.4.0`
| `7.0.0` |
| [github/codeql-action/init](https://github.com/github/codeql-action) |
`4.36.2` | `4.37.0` |
|
[github/codeql-action/autobuild](https://github.com/github/codeql-action)
| `4.36.2` | `4.37.0` |
|
[github/codeql-action/analyze](https://github.com/github/codeql-action)
| `4.36.2` | `4.37.0` |
| [dorny/paths-filter](https://github.com/dorny/paths-filter) | `4.0.1`
| `4.0.2` |


Updates `docker/login-action` from 4.2.0 to 4.4.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/docker/login-action/releases">docker/login-action's
releases</a>.</em></p>
<blockquote>
<h2>v4.4.0</h2>
<ul>
<li>Skip empty <code>registry-auth</code> secret mask by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/login-action/pull/1035">docker/login-action#1035</a></li>
<li>Bump <code>@​aws-sdk/client-ecr</code> and
<code>@​aws-sdk/client-ecr-public</code> to 3.1077.0 <a
href="https://redirect.github.com/docker/login-action/pull/1034">docker/login-action#1034</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/login-action/compare/v4.3.0...v4.4.0">https://github.com/docker/login-action/compare/v4.3.0...v4.4.0</a></p>
<h2>v4.3.0</h2>
<ul>
<li>Preserve names in esbuild bundle by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/login-action/pull/1022">docker/login-action#1022</a></li>
<li>Bump <code>@​aws-sdk/client-ecr</code> and
<code>@​aws-sdk/client-ecr-public</code> to 3.1076.0 <a
href="https://redirect.github.com/docker/login-action/pull/999">docker/login-action#999</a>
<a
href="https://redirect.github.com/docker/login-action/pull/1030">docker/login-action#1030</a></li>
<li>Bump <code>@​docker/actions-toolkit</code> from 0.90.0 to 0.92.0 in
<a
href="https://redirect.github.com/docker/login-action/pull/1004">docker/login-action#1004</a>
<a
href="https://redirect.github.com/docker/login-action/pull/1027">docker/login-action#1027</a></li>
<li>Bump <code>@​sigstore/core</code> from 3.1.0 to 3.2.1 in <a
href="https://redirect.github.com/docker/login-action/pull/1023">docker/login-action#1023</a></li>
<li>Bump <code>@​sigstore/verify</code> from 3.1.0 to 3.1.1 in <a
href="https://redirect.github.com/docker/login-action/pull/1029">docker/login-action#1029</a></li>
<li>Bump http-proxy-agent and https-proxy-agent to 9.1.0 in <a
href="https://redirect.github.com/docker/login-action/pull/1017">docker/login-action#1017</a></li>
<li>Bump js-yaml from 4.1.1 to 5.2.0 in <a
href="https://redirect.github.com/docker/login-action/pull/1028">docker/login-action#1028</a></li>
<li>Bump sigstore from 4.1.0 to 4.1.1 in <a
href="https://redirect.github.com/docker/login-action/pull/1031">docker/login-action#1031</a></li>
<li>Bump tmp from 0.2.5 to 0.2.7 in <a
href="https://redirect.github.com/docker/login-action/pull/1002">docker/login-action#1002</a></li>
<li>Bump undici from 6.24.1 to 6.27.0 in <a
href="https://redirect.github.com/docker/login-action/pull/1020">docker/login-action#1020</a></li>
<li>Bump vite from 7.3.3 to 7.3.6 in <a
href="https://redirect.github.com/docker/login-action/pull/1019">docker/login-action#1019</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/login-action/compare/v4.2.0...v4.3.0">https://github.com/docker/login-action/compare/v4.2.0...v4.3.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/docker/login-action/commit/af1e73f918a031802d376d3c8bbc3fe56130a9b0"><code>af1e73f</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/1034">#1034</a>
from docker/dependabot/npm_and_yarn/aws-sdk-dependen...</li>
<li><a
href="https://github.com/docker/login-action/commit/da722bde43bacb027adfc67d42dbaa4c0f9e550b"><code>da722bd</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/login-action/commit/2916ad60bd5cb72f07aa54c69fdcc61749c09b7a"><code>2916ad6</code></a>
build(deps): bump the aws-sdk-dependencies group across 1 directory with
2 up...</li>
<li><a
href="https://github.com/docker/login-action/commit/ca0a662f786e4cfddce972005bd68f3dafc3a903"><code>ca0a662</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/1035">#1035</a>
from crazy-max/fix-registry-auth-empty-mask</li>
<li><a
href="https://github.com/docker/login-action/commit/c455755a579833bf0d2e4e54e3beb413ef10cc80"><code>c455755</code></a>
chore: update generated content</li>
<li><a
href="https://github.com/docker/login-action/commit/48351901f89581a7c12870c787d3f06d1f498438"><code>4835190</code></a>
skip empty registry-auth secret mask</li>
<li><a
href="https://github.com/docker/login-action/commit/992421c6e6806a7f6df609d1bfff374f9eca3004"><code>992421c</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/1033">#1033</a>
from docker/dependabot/github_actions/docker/bake-ac...</li>
<li><a
href="https://github.com/docker/login-action/commit/b249b43765525dd7951068267a34cf63f22ab4f0"><code>b249b43</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/1032">#1032</a>
from docker/dependabot/github_actions/docker/bake-ac...</li>
<li><a
href="https://github.com/docker/login-action/commit/1b67977736863551a88ff218642a2d7628b10520"><code>1b67977</code></a>
build(deps): bump docker/bake-action from 7.2.0 to 7.3.0</li>
<li><a
href="https://github.com/docker/login-action/commit/9d49d6a3234c78daa10c3c12183ef7b6caa8e69e"><code>9d49d6a</code></a>
build(deps): bump docker/bake-action/subaction/matrix</li>
<li>Additional commits viewable in <a
href="https://github.com/docker/login-action/compare/650006c6eb7dba73a995cc03b0b2d7f5ca915bee...af1e73f918a031802d376d3c8bbc3fe56130a9b0">compare
view</a></li>
</ul>
</details>
<br />

Updates `docker/metadata-action` from 6.1.0 to 6.2.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/docker/metadata-action/releases">docker/metadata-action's
releases</a>.</em></p>
<blockquote>
<h2>v6.2.0</h2>
<ul>
<li>Preserve names in esbuild bundle by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/metadata-action/pull/689">docker/metadata-action#689</a></li>
<li>Bump <code>@​actions/core</code> from 3.0.0 to 3.0.1 in <a
href="https://redirect.github.com/docker/metadata-action/pull/663">docker/metadata-action#663</a></li>
<li>Bump <code>@​actions/github</code> from 9.0.0 to 9.1.1 in <a
href="https://redirect.github.com/docker/metadata-action/pull/666">docker/metadata-action#666</a></li>
<li>Bump <code>@​docker/actions-toolkit</code> from 0.90.0 to 0.92.0 in
<a
href="https://redirect.github.com/docker/metadata-action/pull/672">docker/metadata-action#672</a>
<a
href="https://redirect.github.com/docker/metadata-action/pull/696">docker/metadata-action#696</a></li>
<li>Bump <code>@​sigstore/core</code> from 3.1.0 to 3.2.1 in <a
href="https://redirect.github.com/docker/metadata-action/pull/690">docker/metadata-action#690</a></li>
<li>Bump <code>@​sigstore/verify</code> from 3.1.0 to 3.1.1 in <a
href="https://redirect.github.com/docker/metadata-action/pull/693">docker/metadata-action#693</a></li>
<li>Bump csv-parse from 6.2.1 to 7.0.0 in <a
href="https://redirect.github.com/docker/metadata-action/pull/683">docker/metadata-action#683</a></li>
<li>Bump js-yaml from 4.1.1 to 4.3.0 in <a
href="https://redirect.github.com/docker/metadata-action/pull/688">docker/metadata-action#688</a></li>
<li>Bump moment-timezone from 0.6.1 to 0.6.2 in <a
href="https://redirect.github.com/docker/metadata-action/pull/664">docker/metadata-action#664</a></li>
<li>Bump semver from 7.7.4 to 7.8.5 in <a
href="https://redirect.github.com/docker/metadata-action/pull/665">docker/metadata-action#665</a>
<a
href="https://redirect.github.com/docker/metadata-action/pull/695">docker/metadata-action#695</a></li>
<li>Bump sigstore from 4.1.0 to 4.1.1 in <a
href="https://redirect.github.com/docker/metadata-action/pull/694">docker/metadata-action#694</a></li>
<li>Bump tmp from 0.2.5 to 0.2.7 in <a
href="https://redirect.github.com/docker/metadata-action/pull/673">docker/metadata-action#673</a></li>
<li>Bump undici from 6.25.0 to 6.27.0 in <a
href="https://redirect.github.com/docker/metadata-action/pull/686">docker/metadata-action#686</a></li>
<li>Bump vite from 7.3.2 to 7.3.6 in <a
href="https://redirect.github.com/docker/metadata-action/pull/685">docker/metadata-action#685</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/metadata-action/compare/v6.1.0...v6.2.0">https://github.com/docker/metadata-action/compare/v6.1.0...v6.2.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/docker/metadata-action/commit/dc802804100637a589fabce1cb79ff13a1411302"><code>dc80280</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/metadata-action/issues/696">#696</a>
from docker/dependabot/npm_and_yarn/docker/actions-to...</li>
<li><a
href="https://github.com/docker/metadata-action/commit/2b9fe830efc58cb7b0fab7c94300b1afa01864db"><code>2b9fe83</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/metadata-action/commit/8128ce30ab370fd360c8423698dcc077f2db8087"><code>8128ce3</code></a>
chore(deps): Bump <code>@​docker/actions-toolkit</code> from 0.91.0 to
0.92.0</li>
<li><a
href="https://github.com/docker/metadata-action/commit/1d1c89551edb081628e818368680beffb2bbccee"><code>1d1c895</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/metadata-action/issues/695">#695</a>
from docker/dependabot/npm_and_yarn/semver-7.8.5</li>
<li><a
href="https://github.com/docker/metadata-action/commit/7f0c2dd4c83ea6258f78bf2fc6a02908417518ad"><code>7f0c2dd</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/metadata-action/issues/694">#694</a>
from docker/dependabot/npm_and_yarn/sigstore-4.1.1</li>
<li><a
href="https://github.com/docker/metadata-action/commit/025f8c5c8167edab853571a387d8e80ccc1e91ed"><code>025f8c5</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/metadata-action/commit/e98d63c3081990566e174100a3afaa97aefbbcf3"><code>e98d63c</code></a>
chore(deps): Bump semver from 7.8.1 to 7.8.5</li>
<li><a
href="https://github.com/docker/metadata-action/commit/37d93799507b94cf1c1885efd7278d52a0fb806a"><code>37d9379</code></a>
chore(deps): Bump sigstore from 4.1.0 to 4.1.1</li>
<li><a
href="https://github.com/docker/metadata-action/commit/a1b80728476a956a25c5eeaff91c4dc46e7ac1bc"><code>a1b8072</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/metadata-action/issues/690">#690</a>
from docker/dependabot/npm_and_yarn/sigstore/core-3.2.1</li>
<li><a
href="https://github.com/docker/metadata-action/commit/e0e338106d6ae0da5b44537049a34620b3559a5c"><code>e0e3381</code></a>
[dependabot skip] chore: update generated content</li>
<li>Additional commits viewable in <a
href="https://github.com/docker/metadata-action/compare/80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9...dc802804100637a589fabce1cb79ff13a1411302">compare
view</a></li>
</ul>
</details>
<br />

Updates `docker/setup-buildx-action` from 4.1.0 to 4.2.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/docker/setup-buildx-action/releases">docker/setup-buildx-action's
releases</a>.</em></p>
<blockquote>
<h2>v4.2.0</h2>
<ul>
<li>Preserve names in esbuild bundle by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/572">docker/setup-buildx-action#572</a></li>
<li>Bump <code>@​actions/core</code> from 3.0.0 to 3.0.1 in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/551">docker/setup-buildx-action#551</a></li>
<li>Bump <code>@​docker/actions-toolkit</code> from 0.90.0 to 0.92.0 in
<a
href="https://redirect.github.com/docker/setup-buildx-action/pull/557">docker/setup-buildx-action#557</a>
<a
href="https://redirect.github.com/docker/setup-buildx-action/pull/580">docker/setup-buildx-action#580</a></li>
<li>Bump <code>@​sigstore/core</code> from 3.1.0 to 3.2.1 in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/573">docker/setup-buildx-action#573</a></li>
<li>Bump <code>@​sigstore/verify</code> from 3.1.0 to 3.1.1 in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/576">docker/setup-buildx-action#576</a></li>
<li>Bump js-yaml from 4.1.1 to 5.2.0 in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/562">docker/setup-buildx-action#562</a></li>
<li>Bump sigstore from 4.1.0 to 4.1.1 in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/577">docker/setup-buildx-action#577</a></li>
<li>Bump tmp from 0.2.5 to 0.2.7 in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/556">docker/setup-buildx-action#556</a></li>
<li>Bump undici from 6.25.0 to 6.27.0 in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/570">docker/setup-buildx-action#570</a></li>
<li>Bump vite from 7.3.2 to 7.3.6 in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/569">docker/setup-buildx-action#569</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/setup-buildx-action/compare/v4.1.0...v4.2.0">https://github.com/docker/setup-buildx-action/compare/v4.1.0...v4.2.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/bb05f3f5519dd87d3ba754cc423b652a5edd6d2c"><code>bb05f3f</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/setup-buildx-action/issues/580">#580</a>
from docker/dependabot/npm_and_yarn/docker/actions-to...</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/321c814cb51fbe4af8eca00249525cc0973ea66f"><code>321c814</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/b9a36ef79ba42cfc611885a1e8c388fbf8b8cb3f"><code>b9a36ef</code></a>
build(deps): bump <code>@​docker/actions-toolkit</code> from 0.91.0 to
0.92.0</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/ebeab241289497cd564ac98b3cfc9e64607bb276"><code>ebeab24</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/setup-buildx-action/issues/570">#570</a>
from docker/dependabot/npm_and_yarn/undici-6.27.0</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/5c7b8ae78cec97a3215d4d86679b1d072eaa80cb"><code>5c7b8ae</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/037e618cd98e95e81525b15ff0e9c96f507e6a0e"><code>037e618</code></a>
build(deps): bump undici from 6.25.0 to 6.27.0</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/66080e5802281ec2e72b7f3108915643e702db85"><code>66080e5</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/setup-buildx-action/issues/577">#577</a>
from docker/dependabot/npm_and_yarn/sigstore-4.1.1</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/409aef0aa3f48f0a742e7dec4e0e04ab19afe93c"><code>409aef0</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/setup-buildx-action/issues/562">#562</a>
from docker/dependabot/npm_and_yarn/js-yaml-4.2.0</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/49c6e42949280fa0d70fb327633591be54efbfb6"><code>49c6e42</code></a>
build(deps): bump sigstore from 4.1.0 to 4.1.1</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/2211273e8121ecf9ecb7d6c7c0fcd55526d530c7"><code>2211273</code></a>
[dependabot skip] chore: update generated content</li>
<li>Additional commits viewable in <a
href="https://github.com/docker/setup-buildx-action/compare/d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5...bb05f3f5519dd87d3ba754cc423b652a5edd6d2c">compare
view</a></li>
</ul>
</details>
<br />

Updates `docker/build-push-action` from 7.2.0 to 7.3.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/docker/build-push-action/releases">docker/build-push-action's
releases</a>.</em></p>
<blockquote>
<h2>v7.3.0</h2>
<ul>
<li>Preserve names in esbuild bundle by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/build-push-action/pull/1567">docker/build-push-action#1567</a></li>
<li>Bump <code>@​docker/actions-toolkit</code> from 0.90.0 to 0.92.0 in
<a
href="https://redirect.github.com/docker/build-push-action/pull/1545">docker/build-push-action#1545</a>
<a
href="https://redirect.github.com/docker/build-push-action/pull/1572">docker/build-push-action#1572</a></li>
<li>Bump <code>@​sigstore/core</code> from 3.1.0 to 3.2.1 in <a
href="https://redirect.github.com/docker/build-push-action/pull/1568">docker/build-push-action#1568</a></li>
<li>Bump js-yaml from 4.1.1 to 4.3.0 in <a
href="https://redirect.github.com/docker/build-push-action/pull/1566">docker/build-push-action#1566</a></li>
<li>Bump tmp from 0.2.5 to 0.2.7 in <a
href="https://redirect.github.com/docker/build-push-action/pull/1547">docker/build-push-action#1547</a></li>
<li>Bump undici from 6.24.1 to 6.27.0 in <a
href="https://redirect.github.com/docker/build-push-action/pull/1564">docker/build-push-action#1564</a></li>
<li>Bump vite from 7.3.2 to 7.3.6 in <a
href="https://redirect.github.com/docker/build-push-action/pull/1563">docker/build-push-action#1563</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/build-push-action/compare/v7.2.0...v7.3.0">https://github.com/docker/build-push-action/compare/v7.2.0...v7.3.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/docker/build-push-action/commit/53b7df96c91f9c12dcc8a07bcb9ccacbed38856a"><code>53b7df9</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/build-push-action/issues/1572">#1572</a>
from docker/dependabot/npm_and_yarn/docker/actions-t...</li>
<li><a
href="https://github.com/docker/build-push-action/commit/154298c1ca89be1c0e019084f0611ddca621aafc"><code>154298c</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/build-push-action/commit/cb1238b9c9eb453d106b4e4142a5bd9cde710040"><code>cb1238b</code></a>
chore(deps): Bump <code>@​docker/actions-toolkit</code> from 0.91.0 to
0.92.0</li>
<li><a
href="https://github.com/docker/build-push-action/commit/24f845d5cbe75d2d350a984fd0e18cb7a3f29c1c"><code>24f845d</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/build-push-action/issues/1566">#1566</a>
from docker/dependabot/npm_and_yarn/js-yaml-4.2.0</li>
<li><a
href="https://github.com/docker/build-push-action/commit/9c6973007b52c322651c38915d5e8824cea95c50"><code>9c69730</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/build-push-action/commit/bc3a3a5f72a6dca16c2c2468d1dfc55ee66d2193"><code>bc3a3a5</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/build-push-action/issues/1574">#1574</a>
from docker/dependabot/github_actions/aws-actions/co...</li>
<li><a
href="https://github.com/docker/build-push-action/commit/a82c504a2387bb8bedc50072f9c554ae2a7dab5d"><code>a82c504</code></a>
chore(deps): Bump js-yaml from 4.1.1 to 4.3.0</li>
<li><a
href="https://github.com/docker/build-push-action/commit/0285a75190c039d6dac52b7711abcef3f5d8f6f6"><code>0285a75</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/build-push-action/issues/1573">#1573</a>
from docker/dependabot/github_actions/actions/cache-...</li>
<li><a
href="https://github.com/docker/build-push-action/commit/c6ad2a3f9644680619de938b97c8a10a87b2a88d"><code>c6ad2a3</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/build-push-action/issues/1575">#1575</a>
from docker/dependabot/github_actions/actions/checko...</li>
<li><a
href="https://github.com/docker/build-push-action/commit/d37484fb9737c5442a257e2f0ae5a8d756ed7d92"><code>d37484f</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/build-push-action/issues/1564">#1564</a>
from docker/dependabot/npm_and_yarn/undici-6.27.0</li>
<li>Additional commits viewable in <a
href="https://github.com/docker/build-push-action/compare/f9f3042f7e2789586610d6e8b85c8f03e5195baf...53b7df96c91f9c12dcc8a07bcb9ccacbed38856a">compare
view</a></li>
</ul>
</details>
<br />

Updates `astral-sh/setup-uv` from 8.2.0 to 8.3.2
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/11f9893b081a58869d3b5fccaea48c9e9e46f990"><code>11f9893</code></a>
chore: roll up Dependabot updates (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/948">#948</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/f79855603231e1609d02bec6956bd0e05cbc46b5"><code>f798556</code></a>
docs: update version references to v8.3.1 (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/946">#946</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/e80544d808267c93733c3fd1e2c8c65e0c8707d6"><code>e80544d</code></a>
chore: update known checksums for 0.11.28 (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/947">#947</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/f98e06938123ccabd21905ea5d0069192241f9f1"><code>f98e069</code></a>
Change update-docs PR labels from 'update-docs' to 'documentation' (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/945">#945</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/cd462639a967553a16241af35461402a96978d48"><code>cd46263</code></a>
chore: update known checksums for 0.11.27 (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/944">#944</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/11245c7e122cd1c2297e8115d1e43fe1570f6270"><code>11245c7</code></a>
docs: update version references to v8.3.0 (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/939">#939</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/d31148d669074a8d0a63714ba94f3201e7020bc3"><code>d31148d</code></a>
Strip environment markers from detected uv dependency pins (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/938">#938</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/17c398959b4611a88929fabb5c563a8e43a0ff60"><code>17c3989</code></a>
Fix cache keys for Python version ranges (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/937">#937</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/3cc3c11fdf511cab39136b7c946d973d4ad0df20"><code>3cc3c11</code></a>
chore(deps): roll up Dependabot updates (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/936">#936</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/9225f843d7a9f80a757cf25ef48901fda69ba4bc"><code>9225f84</code></a>
chore(deps): bump release-drafter/release-drafter from 7.3.1 to 7.4.0
(<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/924">#924</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/setup-uv/compare/fac544c07dec837d0ccb6301d7b5580bf5edae39...11f9893b081a58869d3b5fccaea48c9e9e46f990">compare
view</a></li>
</ul>
</details>
<br />

Updates `actions/setup-node` from 6.4.0 to 7.0.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/setup-node/releases">actions/setup-node's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.0</h2>
<h2>What's Changed</h2>
<h3>Enhancements:</h3>
<ul>
<li>Add cache-primary-key and cache-matched-key as outputs by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a> in
<a
href="https://redirect.github.com/actions/setup-node/pull/1577">actions/setup-node#1577</a></li>
<li>Migrate to ESM and upgrade dependencies by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a> in
<a
href="https://redirect.github.com/actions/setup-node/pull/1574">actions/setup-node#1574</a></li>
</ul>
<h3>Bug fixes:</h3>
<ul>
<li>Remove dummy NODE_AUTH_TOKEN export by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a> in
<a
href="https://redirect.github.com/actions/setup-node/pull/1558">actions/setup-node#1558</a></li>
<li>Only use <code>mirrorToken</code> in <code>getManifest</code> if
it's provided by <a
href="https://github.com/deiga"><code>@​deiga</code></a> in <a
href="https://redirect.github.com/actions/setup-node/pull/1548">actions/setup-node#1548</a></li>
</ul>
<h3>Documentation updates:</h3>
<ul>
<li>Add documentation for publishing to npm with Trusted Publisher
(OIDC) by <a
href="https://github.com/chiranjib-swain"><code>@​chiranjib-swain</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1536">actions/setup-node#1536</a></li>
<li>docs: Update restore-only cache documentation by <a
href="https://github.com/priya-kinthali"><code>@​priya-kinthali</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1550">actions/setup-node#1550</a></li>
<li>docs: Update caching recommendations to mitigate cache poisoning
risks by <a
href="https://github.com/chiranjib-swain"><code>@​chiranjib-swain</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1567">actions/setup-node#1567</a></li>
</ul>
<h3>Dependency update:</h3>
<ul>
<li>Upgrade <code>@​actions/cache</code> to 5.1.0, log cache write
denied by <a
href="https://github.com/jasongin"><code>@​jasongin</code></a> in <a
href="https://redirect.github.com/actions/setup-node/pull/1569">actions/setup-node#1569</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/chiranjib-swain"><code>@​chiranjib-swain</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-node/pull/1536">actions/setup-node#1536</a></li>
<li><a href="https://github.com/deiga"><code>@​deiga</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/setup-node/pull/1548">actions/setup-node#1548</a></li>
<li><a href="https://github.com/jasongin"><code>@​jasongin</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-node/pull/1569">actions/setup-node#1569</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-node/compare/v6...v7.0.0">https://github.com/actions/setup-node/compare/v6...v7.0.0</a></p>
<h2>v6.5.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update <code>@​actions/cache</code> to 5.1.0 and add security
overrides for undici and fast-xml-parser by <a
href="https://github.com/HarithaVattikuti"><code>@​HarithaVattikuti</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1579">actions/setup-node#1579</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-node/compare/v6.4.0...v6.5.0">https://github.com/actions/setup-node/compare/v6.4.0...v6.5.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/setup-node/commit/820762786026740c76f36085b0efc47a31fe5020"><code>8207627</code></a>
Migrate to ESM and upgrade dependencies (<a
href="https://redirect.github.com/actions/setup-node/issues/1574">#1574</a>)</li>
<li><a
href="https://github.com/actions/setup-node/commit/04be95cf3511ea51ebf9f224ddfb99cc7ab87cd4"><code>04be95c</code></a>
Add cache-primary-key and cache-matched-key as outputs (<a
href="https://redirect.github.com/actions/setup-node/issues/1577">#1577</a>)</li>
<li><a
href="https://github.com/actions/setup-node/commit/7c2c68d20d402ed6a201ada70a81341941093140"><code>7c2c68d</code></a>
docs: Update caching recommendations to mitigate cache poisoning risks
(<a
href="https://redirect.github.com/actions/setup-node/issues/1567">#1567</a>)</li>
<li><a
href="https://github.com/actions/setup-node/commit/6a61c0375d66246de94630495909f12cf8dac84d"><code>6a61c03</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/setup-node/issues/1569">#1569</a>
from jasongin/update-actions-cache-5.1.0</li>
<li><a
href="https://github.com/actions/setup-node/commit/30eb73b41ded577900c1ebf968ef95cdf8f7434f"><code>30eb73b</code></a>
Resolve high-severity audit issues</li>
<li><a
href="https://github.com/actions/setup-node/commit/4e1a87a501d0302f99e30e2748568adcb388d09f"><code>4e1a87a</code></a>
Update dist</li>
<li><a
href="https://github.com/actions/setup-node/commit/360237f0c01778d0c17291f75c56d6feae4f7574"><code>360237f</code></a>
Strict equality</li>
<li><a
href="https://github.com/actions/setup-node/commit/4f8aac5beb2f0854bc79651567a18c67eb0b9de3"><code>4f8aac5</code></a>
Bump <code>@​actions/cache</code> to 5.1.0, log cache write denied</li>
<li><a
href="https://github.com/actions/setup-node/commit/f4a67bbeca970f103397d3d2b9462cf787cd2980"><code>f4a67bb</code></a>
Only use <code>mirrorToken</code> in <code>getManifest</code> if it's
provided (<a
href="https://redirect.github.com/actions/setup-node/issues/1548">#1548</a>)</li>
<li><a
href="https://github.com/actions/setup-node/commit/0355742c943ddb13ca8a6b700f824231caa91e75"><code>0355742</code></a>
Remove dummy NODE_AUTH_TOKEN export (<a
href="https://redirect.github.com/actions/setup-node/issues/1558">#1558</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/setup-node/compare/48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e...820762786026740c76f36085b0efc47a31fe5020">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/init` from 4.36.2 to 4.37.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/init's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.0</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>v4.36.3</h2>
<p>No user facing changes.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/init's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>4.36.1 - 02 Jun 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.0 - 22 May 2026</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5">2.25.5</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3926">#3926</a></li>
</ul>
<h2>4.35.5 - 15 May 2026</h2>
<ul>
<li>We have improved how the JavaScript bundles for the CodeQL Action
are generated to avoid duplication across bundles and reduce the size of
the repository by around 70%. This should have no effect on the runtime
behaviour of the CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3899">#3899</a></li>
<li>For performance and accuracy reasons, <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> will now only be enabled on a pull request when
diff-informed analysis is also enabled for that run. If diff-informed
analysis is unavailable (for example, because the PR diff ranges could
not be computed), the action will fall back to a full analysis. <a
href="https://redirect.github.com/github/codeql-action/pull/3791">#3791</a></li>
<li>If multiple inputs are provided for the GitHub-internal
<code>analysis-kinds</code> input, only <code>code-scanning</code> will
be enabled. The <code>analysis-kinds</code> input is experimental, for
GitHub-internal use only, and may change without notice at any time. <a
href="https://redirect.github.com/github/codeql-action/pull/3892">#3892</a></li>
<li>Added an experimental change which, when running a Code Scanning
analysis for a PR with <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> enabled, prefers CodeQL CLI versions that have
a cached overlay-base database for the configured languages. This speeds
up analysis for a repository when there is not yet a cached overlay-base
database for the latest CLI version. We expect to roll this change out
to everyone in May. <a
href="https://redirect.github.com/github/codeql-action/pull/3880">#3880</a></li>
</ul>
<h2>4.35.4 - 07 May 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.4">2.25.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3881">#3881</a></li>
</ul>
<h2>4.35.3 - 01 May 2026</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/99df26d4f13ea111d4ec1a7dddef6063f76b97e9"><code>99df26d</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3996">#3996</a>
from github/update-v4.37.0-c7c896d71</li>
<li><a
href="https://github.com/github/codeql-action/commit/31c27074fda95256cda077009907f8a6022dd7c0"><code>31c2707</code></a>
Add changenote for <a
href="https://redirect.github.com/github/codeql-action/issues/3973">#3973</a></li>
<li><a
href="https://github.com/github/codeql-action/commit/72df2181aac054d1f4b44264399d2aac12cf11c6"><code>72df218</code></a>
Update changelog for v4.37.0</li>
<li><a
href="https://github.com/github/codeql-action/commit/c7c896d71b3055d36f2aff93b16bcc6c69923b91"><code>c7c896d</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3995">#3995</a>
from github/update-bundle/codeql-bundle-v2.26.0</li>
<li><a
href="https://github.com/github/codeql-action/commit/3f34ff0ea3f5153c96071437b7cbf71ea3757146"><code>3f34ff0</code></a>
Add changelog note</li>
<li><a
href="https://github.com/github/codeql-action/commit/43bec09f1dc368b430cab4b5d69799bc904079d1"><code>43bec09</code></a>
Update default bundle to codeql-bundle-v2.26.0</li>
<li><a
href="https://github.com/github/codeql-action/commit/f58f0d11ebf5dedd870fab2f999275f7602cfa46"><code>f58f0d1</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3973">#3973</a>
from github/mbg/repo-props/config-file-shorthands</li>
<li><a
href="https://github.com/github/codeql-action/commit/7dc37cbb5b3e37f0e1cd1f18b61e0ea849898fb8"><code>7dc37cb</code></a>
Merge remote-tracking branch 'origin/main' into
mbg/repo-props/config-file-sh...</li>
<li><a
href="https://github.com/github/codeql-action/commit/8e22350a7e28c34c82a5a499fc241923301c2c4f"><code>8e22350</code></a>
Thread <code>ActionState</code> to <code>initConfig</code></li>
<li><a
href="https://github.com/github/codeql-action/commit/69c9e8c7d918cf2fee13b8b72fdde15883ff155b"><code>69c9e8c</code></a>
Mark some <code>status-report</code> imports as <code>type</code>-only
to avoid circular dependencies</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/8aad20d150bbac5944a9f9d289da16a4b0d87c1e...99df26d4f13ea111d4ec1a7dddef6063f76b97e9">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/autobuild` from 4.36.2 to 4.37.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/autobuild's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.0</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>v4.36.3</h2>
<p>No user facing changes.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/autobuild's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>4.36.1 - 02 Jun 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.0 - 22 May 2026</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5">2.25.5</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3926">#3926</a></li>
</ul>
<h2>4.35.5 - 15 May 2026</h2>
<ul>
<li>We have improved how the JavaScript bundles for the CodeQL Action
are generated to avoid duplication across bundles and reduce the size of
the repository by around 70%. This should have no effect on the runtime
behaviour of the CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3899">#3899</a></li>
<li>For performance and accuracy reasons, <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> will now only be enabled on a pull request when
diff-informed analysis is also enabled for that run. If diff-informed
analysis is unavailable (for example, because the PR diff ranges could
not be computed), the action will fall back to a full analysis. <a
href="https://redirect.github.com/github/codeql-action/pull/3791">#3791</a></li>
<li>If multiple inputs are provided for the GitHub-internal
<code>analysis-kinds</code> input, only <code>code-scanning</code> will
be enabled. The <code>analysis-kinds</code> input is experimental, for
GitHub-internal use only, and may change without notice at any time. <a
href="https://redirect.github.com/github/codeql-action/pull/3892">#3892</a></li>
<li>Added an experimental change which, when running a Code Scanning
analysis for a PR with <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> enabled, prefers CodeQL CLI versions that have
a cached overlay-base database for the configured languages. This speeds
up analysis for a repository when there is not yet a cached overlay-base
database for the latest CLI version. We expect to roll this change out
to everyone in May. <a
href="https://redirect.github.com/github/codeql-action/pull/3880">#3880</a></li>
</ul>
<h2>4.35.4 - 07 May 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.4">2.25.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3881">#3881</a></li>
</ul>
<h2>4.35.3 - 01 May 2026</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/99df26d4f13ea111d4ec1a7dddef6063f76b97e9"><code>99df26d</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3996">#3996</a>
from github/update-v4.37.0-c7c896d71</li>
<li><a
href="https://github.com/github/codeql-action/commit/31c27074fda95256cda077009907f8a6022dd7c0"><code>31c2707</code></a>
Add changenote for <a
href="https://redirect.github.com/github/codeql-action/issues/3973">#3973</a></li>
<li><a
href="https://github.com/github/codeql-action/commit/72df2181aac054d1f4b44264399d2aac12cf11c6"><code>72df218</code></a>
Update changelog for v4.37.0</li>
<li><a
href="https://github.com/github/codeql-action/commit/c7c896d71b3055d36f2aff93b16bcc6c69923b91"><code>c7c896d</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3995">#3995</a>
from github/update-bundle/codeql-bundle-v2.26.0</li>
<li><a
href="https://github.com/github/codeql-action/commit/3f34ff0ea3f5153c96071437b7cbf71ea3757146"><code>3f34ff0</code></a>
Add changelog note</li>
<li><a
href="https://github.com/github/codeql-action/commit/43bec09f1dc368b430cab4b5d69799bc904079d1"><code>43bec09</code></a>
Update default bundle to codeql-bundle-v2.26.0</li>
<li><a
href="https://github.com/github/codeql-action/commit/f58f0d11ebf5dedd870fab2f999275f7602cfa46"><code>f58f0d1</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3973">#3973</a>
from github/mbg/repo-props/config-file-shorthands</li>
<li><a
href="https://github.com/github/codeql-action/commit/7dc37cbb5b3e37f0e1cd1f18b61e0ea849898fb8"><code>7dc37cb</code></a>
Merge remote-tracking branch 'origin/main' into
mbg/repo-props/config-file-sh...</li>
<li><a
href="https://github.com/github/codeql-action/commit/8e22350a7e28c34c82a5a499fc241923301c2c4f"><code>8e22350</code></a>
Thread <code>ActionState</code> to <code>initConfig</code></li>
<li><a
href="https://github.com/github/codeql-action/commit/69c9e8c7d918cf2fee13b8b72fdde15883ff155b"><code>69c9e8c</code></a>
Mark some <code>status-report</code> imports as <code>type</code>-only
to avoid circular dependencies</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/8aad20d150bbac5944a9f9d289da16a4b0d87c1e...99df26d4f13ea111d4ec1a7dddef6063f76b97e9">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/analyze` from 4.36.2 to 4.37.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/analyze's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.0</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>v4.36.3</h2>
<p>No user facing changes.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/analyze's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>4.36.1 - 02 Jun 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.0 - 22 May 2026</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5">2.25.5</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3926">#3926</a></li>
</ul>
<h2>4.35.5 - 15 May 2026</h2>
<ul>
<li>We have improved how the JavaScript bundles for the CodeQL Action
are generated to avoid duplication across bundles and reduce the size of
the repository by around 70%. This should have no effect on the runtime
behaviour of the CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3899">#3899</a></li>
<li>For performance and accuracy reasons, <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> will now only be enabled on a pull request when
diff-informed analysis is also enabled for that run. If diff-informed
analysis is unavailable (for example, because the PR diff ranges could
not be computed), the action will fall back to a full analysis. <a
href="https://redirect.github.com/github/codeql-action/pull/3791">#3791</a></li>
<li>If multiple inputs are provided for the GitHub-internal
<code>analysis-kinds</code> input, only <code>code-scanning</code> will
be enabled. The <code>analysis-kinds</code> input is experimental, for
GitHub-internal use only, and may change without notice at any time. <a
href="https://redirect.github.com/github/codeql-action/pull/3892">#3892</a></li>
<li>Added an experimental change which, when running a Code Scanning
analysis for a PR with <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> enabled, prefers CodeQL CLI versions that have
a cached overlay-base database for the configured languages. This speeds
up analysis for a repository when there is not yet a cached overlay-base
database for the latest CLI version. We expect to roll this change out
to everyone in May. <a
href="https://redirect.github.com/github/codeql-action/pull/3880">#3880</a></li>
</ul>
<h2>4.35.4 - 07 May 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.4">2.25.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3881">#3881</a></li>
</ul>
<h2>4.35.3 - 01 May 2026</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/99df26d4f13ea111d4ec1a7dddef6063f76b97e9"><code>99df26d</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3996">#3996</a>
from github/update-v4.37.0-c7c896d71</li>
<li><a
href="https://github.com/github/codeql-action/commit/31c27074fda95256cda077009907f8a6022dd7c0"><code>31c2707</code></a>
Add changenote for <a
href="https://redirect.github.com/github/codeql-action/issues/3973">#3973</a></li>
<li><a
href="https://github.com/github/codeql-action/commit/72df2181aac054d1f4b44264399d2aac12cf11c6"><code>72df218</code></a>
Update changelog for v4.37.0</li>
<li><a
href="https://github.com/github/codeql-action/commit/c7c896d71b3055d36f2aff93b16bcc6c69923b91"><code>c7c896d</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3995">#3995</a>
from github/update-bundle/codeql-bundle-v2.26.0</li>
<li><a
href="https://github.com/github/codeql-action/commit/3f34ff0ea3f5153c96071437b7cbf71ea3757146"><code>3f34ff0</code></a>
Add changelog note</li>
<li><a
href="https://github.com/github/codeql-action/commit/43bec09f1dc368b430cab4b5d69799bc904079d1"><code>43bec09</code></a>
Update default bundle to codeql-bundle-v2.26.0</li>
<li><a
href="https://github.com/github/codeql-action/commit/f58f0d11ebf5dedd870fab2f999275f7602cfa46"><code>f58f0d1</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3973">#3973</a>
from github/mbg/repo-props/config-file-shorthands</li>
<li><a
href="https://github.com/github/codeql-action/commit/7dc37cbb5b3e37f0e1cd1f18b61e0ea849898fb8"><code>7dc37cb</code></a>
Merge remote-tracking branch 'origin/main' into
mbg/repo-props/config-file-sh...</li>
<li><a
href="https://github.com/github/codeql-action/commit/8e22350a7e28c34c82a5a499fc241923301c2c4f"><code>8e22350</code></a>
Thread <code>ActionState</code> to <code>initConfig</code></li>
<li><a
href="https://github.com/github/codeql-action/commit/69c9e8c7d918cf2fee13b8b72fdde15883ff155b"><code>69c9e8c</code></a>
Mark some <code>status-report</code> imports as <code>type</code>-only
to avoid circular dependencies</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/8aad20d150bbac5944a9f9d289da16a4b0d87c1e...99df26d4f13ea111d4ec1a7dddef6063f76b97e9">compare
view</a></li>
</ul>
</details>
<br />

Updates `dorny/paths-filter` from 4.0.1 to 4.0.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/dorny/paths-filter/releases">dorny/paths-filter's
releases</a>.</em></p>
<blockquote>
<h2>v4.0.2</h2>
<h2>What's Changed</h2>
<ul>
<li>fix warning message by <a
href="https://github.com/cgundy"><code>@​cgundy</code></a> in <a
href="https://redirect.github.com/dorny/paths-filter/pull/282">dorny/paths-filter#282</a></li>
<li>chore: fix GitHub spelling in logs by <a
href="https://github.com/squat"><code>@​squat</code></a> in <a
href="https://redirect.github.com/dorny/paths-filter/pull/278">dorny/paths-filter#278</a></li>
<li>fix: use rev-parse instead of branch --show-current for older git
compat by <a
href="https://github.com/saschabratton"><code>@​saschabratton</code></a>
in <a
href="https://redirect.github.com/dorny/paths-filter/pull/303">dorny/paths-filter#303</a></li>
<li>fix: work around git dubious ownership errors in container jobs by
<a
href="https://github.com/saschabratton"><code>@​saschabratton</code></a>
in <a
href="https://redirect.github.com/dorny/paths-filter/pull/317">dorny/paths-filter#317</a></li>
<li>docs: update changelog for v4.0.2 by <a
href="https://github.com/saschabratton"><code>@​saschabratton</code></a>
in <a
href="https://redirect.github.com/dorny/paths-filter/pull/318">dorny/paths-filter#318</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/cgundy"><code>@​cgundy</code></a> made
their first contribution in <a
href="https://redirect.github.com/dorny/paths-filter/pull/282">dorny/paths-filter#282</a></li>
<li><a href="https://github.com/squat"><code>@​squat</code></a> made
their first contribution in <a
href="https://redirect.github.com/dorny/paths-filter/pull/278">dorny/paths-filter#278</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/dorny/paths-filter/compare/v4.0.1...v4.0.2">https://github.com/dorny/paths-filter/compare/v4.0.1...v4.0.2</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md">dorny/paths-filter's
changelog</a>.</em></p>
<blockquote>
<h2>v4.0.2</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/317">Work
around git dubious ownership errors in container jobs</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/303">Use
rev-parse instead of branch --show-current for older git compat</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/282">Fix
warning message</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/dorny/paths-filter/commit/7b450fff21473bca461d4b92ce414b9d0420d706"><code>7b450ff</code></a>
docs: update changelog for v4.0.2 (<a
href="https://redirect.github.com/dorny/paths-filter/issues/318">#318</a>)</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/928037783a71f24983ea250c6e55290c1b2de54f"><code>9280377</code></a>
fix: work around git dubious ownership errors in container jobs (<a
href="https://redirect.github.com/dorny/paths-filter/issues/317">#317</a>)</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/f3ceefdc7ef57bc2d8560787d4b6c33e44044cec"><code>f3ceefd</code></a>
fix: use rev-parse instead of branch --show-current for older git compat
(<a
href="https://redirect.github.com/dorny/paths-filter/issues/303">#303</a>)</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/61f87a10cd2c304679af17bb73ef192addf33c1c"><code>61f87a1</code></a>
chore: fix GitHub spelling in logs (<a
href="https://redirect.github.com/dorny/paths-filter/issues/278">#278</a>)</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/b82ff81ffbe6fb4b636bb5b47e37fd8d12b32632"><code>b82ff81</code></a>
fix warning message (<a
href="https://redirect.github.com/dorny/paths-filter/issues/282">#282</a>)</li>
<li>See full diff in <a
href="https://github.com/dorny/paths-filter/compare/v4.0.1...v4.0.2">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-19 02:51:53 -04:00
dependabot[bot] ceaccfd29f Bump prek from 0.4.8 to 0.4.9 in the python-deps group (#1116)
Bumps the python-deps group with 1 update:
[prek](https://github.com/j178/prek).

Updates `prek` from 0.4.8 to 0.4.9
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/releases">prek's
releases</a>.</em></p>
<blockquote>
<h2>0.4.9</h2>
<h2>Release Notes</h2>
<p>Released on 2026-07-11.</p>
<p>Note: This release changes the repository identity schema stored in
cached hook environments. Existing
hook environments will be invalidated, and prek will reinstall them
automatically when needed.</p>
<h3>Enhancements</h3>
<ul>
<li>Preserve additional dependency order (<a
href="https://redirect.github.com/j178/prek/pull/2311">#2311</a>)</li>
<li>Remove parallel Ruby gem installation (<a
href="https://redirect.github.com/j178/prek/pull/2307">#2307</a>)</li>
<li>Warn for missing update repositories (<a
href="https://redirect.github.com/j178/prek/pull/2316">#2316</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Fix mixed workspace selectors (<a
href="https://redirect.github.com/j178/prek/pull/2306">#2306</a>)</li>
<li>Fix try-repo local path resolution (<a
href="https://redirect.github.com/j178/prek/pull/2310">#2310</a>)</li>
<li>Use resolved gem executable (<a
href="https://redirect.github.com/j178/prek/pull/2308">#2308</a>)</li>
<li>Validate complete XML documents in check-xml (<a
href="https://redirect.github.com/j178/prek/pull/2312">#2312</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/j178"><code>@​j178</code></a></li>
</ul>
<h2>Install prek 0.4.9</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://github.com/j178/prek/releases/download/v0.4.9/prek-installer.sh
| sh
</code></pre>
<h3>Install prebuilt binaries via powershell script</h3>
<pre lang="sh"><code>powershell -ExecutionPolicy Bypass -c &quot;irm
https://github.com/j178/prek/releases/download/v0.4.9/prek-installer.ps1
| iex&quot;
</code></pre>
<h3>Install prebuilt binaries via Homebrew</h3>
<pre lang="sh"><code>brew install prek
</code></pre>
<h2>Download prek 0.4.9</h2>
<table>
<thead>
<tr>
<th>File</th>
<th>Platform</th>
<th>Checksum</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.4.9/prek-aarch64-apple-darwin.tar.gz">prek-aarch64-apple-darwin.tar.gz</a></td>
<td>Apple Silicon macOS</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.4.9/prek-aarch64-apple-darwin.tar.gz.sha256">checksum</a></td>
</tr>
</tbody>
</table>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/blob/master/CHANGELOG.md">prek's
changelog</a>.</em></p>
<blockquote>
<h2>0.4.9</h2>
<p>Released on 2026-07-11.</p>
<p>Note: This release changes the repository identity schema stored in
cached hook environments. Existing
hook environments will be invalidated, and prek will reinstall them
automatically when needed.</p>
<h3>Enhancements</h3>
<ul>
<li>Preserve additional dependency order (<a
href="https://redirect.github.com/j178/prek/pull/2311">#2311</a>)</li>
<li>Remove parallel Ruby gem installation (<a
href="https://redirect.github.com/j178/prek/pull/2307">#2307</a>)</li>
<li>Warn for missing update repositories (<a
href="https://redirect.github.com/j178/prek/pull/2316">#2316</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Fix mixed workspace selectors (<a
href="https://redirect.github.com/j178/prek/pull/2306">#2306</a>)</li>
<li>Fix try-repo local path resolution (<a
href="https://redirect.github.com/j178/prek/pull/2310">#2310</a>)</li>
<li>Use resolved gem executable (<a
href="https://redirect.github.com/j178/prek/pull/2308">#2308</a>)</li>
<li>Validate complete XML documents in check-xml (<a
href="https://redirect.github.com/j178/prek/pull/2312">#2312</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/j178"><code>@​j178</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/j178/prek/commit/42b79a57f275ffe3e7fc3ad32fdf2754416779d5"><code>42b79a5</code></a>
Bump version to 0.4.9 (<a
href="https://redirect.github.com/j178/prek/issues/2318">#2318</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/ecbd9dabab540d1d964a66276f70ff084c5aa14d"><code>ecbd9da</code></a>
Extract executable helpers (<a
href="https://redirect.github.com/j178/prek/issues/2317">#2317</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/25ee3c2d16086291673fed03dcc9beff09b1e03a"><code>25ee3c2</code></a>
Validate complete XML documents in check-xml (<a
href="https://redirect.github.com/j178/prek/issues/2312">#2312</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/876f7610dcc6f50dd6b85842cc5f01b36e7dc93f"><code>876f761</code></a>
Warn for missing update repositories (<a
href="https://redirect.github.com/j178/prek/issues/2316">#2316</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/61cbcbf91b0c6478afcc16a2de8fb51269c94d52"><code>61cbcbf</code></a>
Preserve additional dependency order (<a
href="https://redirect.github.com/j178/prek/issues/2311">#2311</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/4b006eba7adf4515f686647060fa852fd994359d"><code>4b006eb</code></a>
Update Rust to 1.97 (<a
href="https://redirect.github.com/j178/prek/issues/2313">#2313</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/40f6609fd824151af0cb85ca994a1cd2f6ea5d87"><code>40f6609</code></a>
Fix try-repo local path resolution (<a
href="https://redirect.github.com/j178/prek/issues/2310">#2310</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/3795e9964883a2e57ed9b783345ac1cf654160a3"><code>3795e99</code></a>
Use resolved gem executable (<a
href="https://redirect.github.com/j178/prek/issues/2308">#2308</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/de7ac64173f48f91ad746e1ebe7e8b4cd1a99d11"><code>de7ac64</code></a>
Remove parallel Ruby gem installation (<a
href="https://redirect.github.com/j178/prek/issues/2307">#2307</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/434e791453272d9badef7c5c6295342d541496ac"><code>434e791</code></a>
Fix mixed workspace selectors (<a
href="https://redirect.github.com/j178/prek/issues/2306">#2306</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/j178/prek/compare/v0.4.8...v0.4.9">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=prek&package-manager=uv&previous-version=0.4.8&new-version=0.4.9)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-15 10:28:00 -04:00
CaliBrain 6bab9989ab fix: fetch tracker .torrent links once per add and surface fetch failures (#1115)
## Summary

Fixes Prowlarr torrent downloads that fail with `Could not determine
torrent hash
from URL` when the result has no magnet link and no infohash (e.g.
MyAnonaMouse),
where fetching the .torrent from Prowlarr's proxy download link is the
only path.

Two problems compounded here:

1. **Every add attempt fetched the download link twice.**
`find_existing()`
prefetched the .torrent to compute a dedup hash, discarded the result,
and
`add_download()` fetched the same URL again seconds later. Private
tracker
links behind Prowlarr's proxy can be slow, rate-limited, or effectively
single-use, so the second hit could fail even when the link itself was
valid —
   which is why the reporter's manual fetch of the same URL succeeded.
2. **The real failure reason was invisible.** When the fetch failed
(e.g.
Prowlarr returning HTTP 500 because the tracker rejected the request —
see the
2026-07-07 MAM report on #476, which turned out to be a MAM IP-settings
problem), the reason was logged at DEBUG only and the user saw the
misleading
   generic hash error.

## What changed

- `extract_torrent_info()` now reuses a recent successful fetch of the
same URL
(short-TTL in-memory cache, successes only), so one add attempt hits the
tracker download link exactly once across `find_existing()` +
`add_download()`.
All four torrent clients (qBittorrent, Deluge, Transmission, rTorrent)
share
  this path and benefit. Failures are never cached, so retries refetch.
- `TorrentInfo` gains a `fetch_error` field. qBittorrent and rTorrent
append it
to the hash error (`... (torrent file fetch failed: 500 Server Error
...)`),
Deluge to its "Failed to fetch torrent file" error. The enriched message
still
contains the exact substring the #1109 expired-link refresh hook matches
on,
  so the refresh-and-retry path keeps working.
- Torrent fetch failures are logged at WARNING instead of DEBUG, so
non-debug
  logs show the cause.

## Validation

- `uv run pytest tests/prowlarr tests/download -q` — 498 passed
- `uv run pytest tests/newznab tests/audiobookbay -q` — 147 passed
- `uv run ruff check` / `ruff format --check` on all changed files
- New tests: fetch-cache reuse, failure-not-cached + reason capture,
expected-hash fallback on failed/hashless fetches, TTL expiry,
magnet-redirect
  reuse, and the enriched qBittorrent error message.

Fixes #1111
2026-07-14 16:21:31 -04:00
Aidan AbbottandAidan Abbott f8d3f990ca fix: Prowlarr expired download link refresh (#1109)
## Summary

Fixes Prowlarr downloads that fail after a queued torrent result’s
tracker download link expires.

Prowlarr torrent results can expose a `downloadUrl` that is only a
short-lived proxy to the upstream tracker. Some trackers, including MAM,
include expiring credentials in that URL. Shelfmark was persisting that
URL as retry data and later treating it as durable. If the in-memory
Prowlarr cache was gone, or if qBittorrent tried to add a stale URL,
Shelfmark could fail with a misleading torrent-hash error instead of
refreshing the release.

## What changed

- Stop persisting Prowlarr `downloadUrl` values as durable retry data.
- Persist only source context needed to refresh the release later.
- On a Prowlarr cache miss, re-query Prowlarr using the queued task
context.
- Accept refreshed results only when the stable identity matches the
original release:
  - `guid == task.task_id`
  - or `infoUrl == task.task_id`
- Cache the fresh raw Prowlarr result and build the download request
from its current `downloadUrl` / `magnetUrl`.
- If qBittorrent add fails with `Could not determine torrent hash from
URL`, remove the stale cached Prowlarr result, refresh once, and retry
with the fresh URL.
- Preserve existing magnet handling: torrent results continue to use
`magnetUrl` first, so the refresh path only targets Prowlarr proxy
`downloadUrl` values that can expire.
- Improve the user-facing failure when refresh cannot find the same
release:
`The indexer download link expired and the release could not be
refreshed. Search again for a fresh result.`

## Why this approach

The important constraint is avoiding accidental downloads of a different
edition or format after the original tracker link expires. Re-running a
search by title can return many plausible results, so the refresh path
deliberately requires an exact stable identity match before using any
new URL.

This treats Prowlarr `downloadUrl` as a short-lived hint, while still
allowing retries to recover when Prowlarr can find the same release
again. The one-shot retry after qBittorrent add failure handles the case
where Shelfmark still has a cached Prowlarr result, but that cached
result contains an expired proxy URL.

The refresh hook is source-specific and defaults to no-op for other
external download handlers, so Newznab and other sources keep their
existing retry behavior.

## Bug

Fixes #1012

Context:
https://github.com/calibrain/shelfmark/issues/1012#issuecomment-4917148398

## Validation

- `uv run pytest tests/prowlarr -q`
- `uv run pytest tests/download/test_orchestrator_user_output_mode.py
-q`
- `uv run pytest tests/newznab/test_handler.py
tests/audiobookbay/test_handler.py -q`
- `uv run ruff check shelfmark/core/models.py
shelfmark/download/orchestrator.py
shelfmark/download/clients/base_handler.py
shelfmark/release_sources/prowlarr/handler.py
tests/prowlarr/test_handler.py
tests/prowlarr/test_integration_handler.py
tests/prowlarr/test_failure_scenarios.py tests/prowlarr/test_source.py
tests/download/test_orchestrator_user_output_mode.py`
- after resolving conflicts with latest `main`: `uv run pytest
tests/prowlarr -q`
- after resolving conflicts with latest `main`: `uv run ruff check
shelfmark/release_sources/prowlarr/handler.py`
- after resolving conflicts with latest `main`: `uv run pytest
tests/prowlarr/test_handler.py tests/prowlarr/test_source.py -q`

Co-authored-by: Aidan Abbott <aidanabbott@Aidans-MacBook-Pro.local>
2026-07-14 14:22:29 -04:00
dependabot[bot] 30d7f228be Bump the python-deps group with 5 updates (#1114)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 14:07:49 -04:00
adman234 b291df5cc9 fix(prowlarr): prevent silent loss of indexer seed limits on transient Prowlarr API failures (#1110)
# fix(prowlarr): prevent silent loss of indexer seed limits

Related to #795, though not a fix for that specific (closed) report —
see note below.

## Problem

With "Use Prowlarr seed preferences" enabled, ~5–10% of torrent grabs
are added to the download client without their configured share limits
and seed indefinitely (∞ ETA in qBittorrent).

Seed limits are resolved once at search time.
`get_indexer_seed_settings()` builds on `get_indexers()`, which swallows
all API errors and returns `[]`. A transient failure of the
`/api/v1/indexer` call therefore produces an empty settings dict that is
indistinguishable from "no limits configured", while the search itself
(separate HTTP calls) still succeeds. Every result from that search is
cached without `configuredSeedTimeMinutes`; grabbing one sends the
torrent to the client with no limits.

Compounding factors: `cache_release()` is last-write-wins by GUID, so
one degraded search can strip enrichment from a previously good cache
entry; and the retry fields persisted at queue time snapshot the same
missing values, so retries reproduce the failure.

## Changes

- **`api.py`** — `get_indexers()` / `get_enabled_indexers_detailed()`
gain a keyword-only `raise_on_error` (default `False`, existing behavior
unchanged). `get_indexer_seed_settings()` uses it, so fetch failures now
propagate and an empty dict strictly means "nothing configured".
- **`source.py`** — searches fetch settings via
`_fetch_indexer_seed_settings()`, which maintains a module-level
last-known-good copy (merged on each success) and falls back to it with
a warning when the fetch fails. After one successful fetch, results can
no longer be cached un-enriched.
- **`handler.py`** — grab-time safety net in `_resolve_download()`: if
seed preferences are enabled, the release is a torrent, and no
configured limits are present in the cached result, the handler
re-resolves the limits from Prowlarr for that indexer
(`restrict_to=[indexerId]`) before adding to the client. If limits still
can't be resolved, a warning is logged so the condition is visible
instead of silent.
- **Tests** — regression coverage: last-known-good fallback (success
updates cache, failure falls back, failure with no history returns
empty, fallback copy is mutation-safe) and grab-time fallback (used when
enrichment is missing, tolerates Prowlarr being down, skipped when
enrichment is present). Existing test stubs for
`get_enabled_indexers_detailed` updated to accept the new kwarg.

## Testing

- `uv run pytest tests/prowlarr/test_handler.py
tests/prowlarr/test_source.py
tests/prowlarr/test_integration_handler.py` — 93 passed, 2 skipped
(Python 3.14.4)
- Full `tests/prowlarr` run has 11 pre-existing failures on this
environment (Windows path-separator assertions in the
qBittorrent/NZBGet/SABnzbd/Transmission client tests, e.g.
`/downloads/x` vs `\downloads\x`); confirmed these also fail on
unpatched `main` and are unrelated to this change
- `uv run ruff check` / `ruff format --check` — clean on touched files
- `uv run basedpyright` — 0 errors on touched files

No behavior change when `PROWLARR_USE_SEED_PREFERENCES` is disabled; the
fallback path only activates when the preference is on and enrichment is
missing for a torrent.

---

**Note on #795:** this PR references #795 for background context on the
seed-limits feature, but it does not fix that report — #795 was about
seed limits not being converted/applied at all (a units mismatch), and
was already fixed by #946 / #959. This PR fixes a separate,
still-present bug: `get_indexers()` silently swallowing transient API
errors, which intermittently drops seed limits even when the feature is
otherwise working correctly.
2026-07-09 23:25:29 -04:00
CaliBrain 9b1d4322b7 fix: resolve remaining "Could not determine torrent hash from URL" failures (#1012) (#1108) 2026-07-08 14:20:31 -04:00
dependabot[bot] d7a21ea248 Bump the python-deps group with 2 updates (#1107)
Bumps the python-deps group with 2 updates:
[apprise](https://github.com/caronc/apprise) and
[prek](https://github.com/j178/prek).

Updates `apprise` from 1.11.0 to 1.12.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/caronc/apprise/releases">apprise's
releases</a>.</em></p>
<blockquote>
<h2>Release v1.12.0</h2>
<h2>What's Changed</h2>
<p>Apprise now supports more than 150 services! HTML -&gt; Markdown
Support now built into Apprise. But otherwise this is just a monthly
release bundling all of the bugfixes, enhancments and new services that
have accumulated since the last. <a
href="https://appriseit.com">https://appriseit.com</a> under went a few
renovations of it's own; new icons; a much improved and more useful <a
href="https://appriseit.com/url-builder/">URL Builder</a> and and
improved the search functionality on <a
href="https://appriseit.com/services/">Service List section</a>.</p>
<h3>📣 New Notification Services:</h3>
<ul>
<li>Added Stackfield support in <a
href="https://redirect.github.com/caronc/apprise/pull/1651">caronc/apprise#1651</a></li>
<li>Added PushWard Support by <a
href="https://github.com/mac-lucky"><code>@​mac-lucky</code></a> in <a
href="https://redirect.github.com/caronc/apprise/pull/1643">caronc/apprise#1643</a></li>
<li>MailerSend Support added in <a
href="https://redirect.github.com/caronc/apprise/pull/1646">caronc/apprise#1646</a></li>
<li>Added Notifyer Support in <a
href="https://redirect.github.com/caronc/apprise/pull/1648">caronc/apprise#1648</a></li>
<li>Added support for 800.com in <a
href="https://redirect.github.com/caronc/apprise/pull/1647">caronc/apprise#1647</a></li>
<li>Added Kook Support in <a
href="https://redirect.github.com/caronc/apprise/pull/1654">caronc/apprise#1654</a></li>
<li>Serwer SMS Support Added in <a
href="https://redirect.github.com/caronc/apprise/pull/1657">caronc/apprise#1657</a></li>
<li>Added SMSC Support in <a
href="https://redirect.github.com/caronc/apprise/pull/1658">caronc/apprise#1658</a></li>
<li>Add Flowtriq notification service for DDoS alerting by <a
href="https://github.com/jacob-masse"><code>@​jacob-masse</code></a> in
<a
href="https://redirect.github.com/caronc/apprise/pull/1644">caronc/apprise#1644</a></li>
</ul>
<h3>🐞  Bugfixes</h3>
<ul>
<li>Fix Slack webhook botname/default identity behavior by <a
href="https://github.com/vgopalakPrgs"><code>@​vgopalakPrgs</code></a>
in <a
href="https://redirect.github.com/caronc/apprise/pull/1634">caronc/apprise#1634</a></li>
<li>Fix OneSignal url() doubling @/# target prefixes on reload by <a
href="https://github.com/sarathfrancis90"><code>@​sarathfrancis90</code></a>
in <a
href="https://redirect.github.com/caronc/apprise/pull/1649">caronc/apprise#1649</a></li>
<li>Verify SMTP TLS certificates for SMTPS and STARTTLS connections by
<a
href="https://github.com/sarathfrancis90"><code>@​sarathfrancis90</code></a>
in <a
href="https://redirect.github.com/caronc/apprise/pull/1640">caronc/apprise#1640</a></li>
<li>name= support fixed on http:// attachments in <a
href="https://redirect.github.com/caronc/apprise/pull/1645">caronc/apprise#1645</a></li>
</ul>
<h3>💡 Features</h3>
<ul>
<li>🔥 HTML to Markdown support in <a
href="https://redirect.github.com/caronc/apprise/pull/1638">caronc/apprise#1638</a></li>
<li>regex improvements to token parsing in <a
href="https://redirect.github.com/caronc/apprise/pull/1650">caronc/apprise#1650</a></li>
<li>Discord templating support added in <a
href="https://redirect.github.com/caronc/apprise/pull/1653">caronc/apprise#1653</a></li>
<li>support multi-attachments in single in discord message in <a
href="https://redirect.github.com/caronc/apprise/pull/1655">caronc/apprise#1655</a></li>
<li>Added RFC 2387 inline attachments to email:// in <a
href="https://redirect.github.com/caronc/apprise/pull/1659">caronc/apprise#1659</a></li>
<li>Support same-level yaml indented URL configuration entries in <a
href="https://redirect.github.com/caronc/apprise/pull/1636">caronc/apprise#1636</a></li>
<li>XMPP SASL improvments and CLI Debug output added in <a
href="https://redirect.github.com/caronc/apprise/pull/1637">caronc/apprise#1637</a></li>
<li>Support MS Teams user mentions by <a
href="https://github.com/caronc"><code>@​caronc</code></a> in <a
href="https://redirect.github.com/caronc/apprise/pull/1660">caronc/apprise#1660</a></li>
</ul>
<h3>❤️ Life-Cycle Support</h3>
<ul>
<li>removed popcorn notify plugin (no longer in service) in <a
href="https://redirect.github.com/caronc/apprise/pull/1656">caronc/apprise#1656</a></li>
<li>Update README.md to correct typo by <a
href="https://github.com/aherbert"><code>@​aherbert</code></a> in <a
href="https://redirect.github.com/caronc/apprise/pull/1642">caronc/apprise#1642</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/aherbert"><code>@​aherbert</code></a>
made their first contribution in <a
href="https://redirect.github.com/caronc/apprise/pull/1642">caronc/apprise#1642</a></li>
<li><a
href="https://github.com/jacob-masse"><code>@​jacob-masse</code></a>
made their first contribution in <a
href="https://redirect.github.com/caronc/apprise/pull/1644">caronc/apprise#1644</a></li>
<li><a href="https://github.com/mac-lucky"><code>@​mac-lucky</code></a>
made their first contribution in <a
href="https://redirect.github.com/caronc/apprise/pull/1643">caronc/apprise#1643</a></li>
<li><a
href="https://github.com/sarathfrancis90"><code>@​sarathfrancis90</code></a>
made their first contribution in <a
href="https://redirect.github.com/caronc/apprise/pull/1640">caronc/apprise#1640</a></li>
<li><a
href="https://github.com/vgopalakPrgs"><code>@​vgopalakPrgs</code></a>
made their first contribution in <a
href="https://redirect.github.com/caronc/apprise/pull/1634">caronc/apprise#1634</a></li>
</ul>
<h2>Installation</h2>
<p>Apprise is available <a href="https://pypi.org/project/apprise/">on
PyPI</a> through <em>pip</em>:</p>
<pre lang="bash"><code># Install Apprise v1.12.0 from PyPI
pip install apprise==1.12.0
</code></pre>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/caronc/apprise/compare/v1.11.0...v1.12.0">https://github.com/caronc/apprise/compare/v1.11.0...v1.12.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/caronc/apprise/commit/c067047a3d18a81af88c7ec90d38cb1aa0fb7564"><code>c067047</code></a>
bumped version to v1.12.0</li>
<li><a
href="https://github.com/caronc/apprise/commit/183ca0c9a2f74c1c05881900409a579fa8e74a76"><code>183ca0c</code></a>
Support MS Teams user mentions (<a
href="https://redirect.github.com/caronc/apprise/issues/1660">#1660</a>)</li>
<li><a
href="https://github.com/caronc/apprise/commit/493e72eada5bce551ea12aab87403d2a5c435a9b"><code>493e72e</code></a>
Added RFC 2387 inline attachments to email:// (<a
href="https://redirect.github.com/caronc/apprise/issues/1659">#1659</a>)</li>
<li><a
href="https://github.com/caronc/apprise/commit/44c7ce6937385647c429cfd06bf5a441c1de6625"><code>44c7ce6</code></a>
Added SMSC Support (<a
href="https://redirect.github.com/caronc/apprise/issues/1658">#1658</a>)</li>
<li><a
href="https://github.com/caronc/apprise/commit/356e0fbc58c9383e3cbe481cae8f841257ee7366"><code>356e0fb</code></a>
Serwer SMS Support Added (<a
href="https://redirect.github.com/caronc/apprise/issues/1657">#1657</a>)</li>
<li><a
href="https://github.com/caronc/apprise/commit/2053e6afcd4e16ecdffce1423acf9227ba4a6b80"><code>2053e6a</code></a>
improved debug logging when upstream json response is bad</li>
<li><a
href="https://github.com/caronc/apprise/commit/8c4ecb0b8ee68b29ed2915551dccec982162b22f"><code>8c4ecb0</code></a>
Added Kook Support (<a
href="https://redirect.github.com/caronc/apprise/issues/1654">#1654</a>)</li>
<li><a
href="https://github.com/caronc/apprise/commit/c98567efbc3f31cfc7281d10cc31d2fc64de4fa0"><code>c98567e</code></a>
support multi-attachments in single in discord message (<a
href="https://redirect.github.com/caronc/apprise/issues/1655">#1655</a>)</li>
<li><a
href="https://github.com/caronc/apprise/commit/23782c1bff299e218d5d32bfc387ff63080e8989"><code>23782c1</code></a>
removed popcorn notify plugin (no longer in service) (<a
href="https://redirect.github.com/caronc/apprise/issues/1656">#1656</a>)</li>
<li><a
href="https://github.com/caronc/apprise/commit/713a745230235161d5cf372c02d21e762f0c8def"><code>713a745</code></a>
Discord templating support added (<a
href="https://redirect.github.com/caronc/apprise/issues/1653">#1653</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/caronc/apprise/compare/v1.11.0...v1.12.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `prek` from 0.4.6 to 0.4.8
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/releases">prek's
releases</a>.</em></p>
<blockquote>
<h2>0.4.8</h2>
<h2>Release Notes</h2>
<p>Released on 2026-07-04.</p>
<h3>Enhancements</h3>
<ul>
<li>Add default_env configuration (<a
href="https://redirect.github.com/j178/prek/pull/2288">#2288</a>)</li>
<li>Rename <code>auto-update</code> to <code>update</code> (<a
href="https://redirect.github.com/j178/prek/pull/2286">#2286</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Fix progress collapse ordering (<a
href="https://redirect.github.com/j178/prek/pull/2291">#2291</a>)</li>
<li>Fix progress insertion after collapsed rows (<a
href="https://redirect.github.com/j178/prek/pull/2292">#2292</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/j178"><code>@​j178</code></a></li>
</ul>
<h2>Install prek 0.4.8</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://github.com/j178/prek/releases/download/v0.4.8/prek-installer.sh
| sh
</code></pre>
<h3>Install prebuilt binaries via powershell script</h3>
<pre lang="sh"><code>powershell -ExecutionPolicy Bypass -c &quot;irm
https://github.com/j178/prek/releases/download/v0.4.8/prek-installer.ps1
| iex&quot;
</code></pre>
<h3>Install prebuilt binaries via Homebrew</h3>
<pre lang="sh"><code>brew install prek
</code></pre>
<h2>Download prek 0.4.8</h2>
<table>
<thead>
<tr>
<th>File</th>
<th>Platform</th>
<th>Checksum</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.4.8/prek-aarch64-apple-darwin.tar.gz">prek-aarch64-apple-darwin.tar.gz</a></td>
<td>Apple Silicon macOS</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.4.8/prek-aarch64-apple-darwin.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.4.8/prek-x86_64-apple-darwin.tar.gz">prek-x86_64-apple-darwin.tar.gz</a></td>
<td>Intel macOS</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.4.8/prek-x86_64-apple-darwin.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.4.8/prek-aarch64-pc-windows-msvc.zip">prek-aarch64-pc-windows-msvc.zip</a></td>
<td>ARM64 Windows</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.4.8/prek-aarch64-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.4.8/prek-i686-pc-windows-msvc.zip">prek-i686-pc-windows-msvc.zip</a></td>
<td>x86 Windows</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.4.8/prek-i686-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.4.8/prek-x86_64-pc-windows-msvc.zip">prek-x86_64-pc-windows-msvc.zip</a></td>
<td>x64 Windows</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.4.8/prek-x86_64-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.4.8/prek-aarch64-unknown-linux-gnu.tar.gz">prek-aarch64-unknown-linux-gnu.tar.gz</a></td>
<td>ARM64 Linux</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.4.8/prek-aarch64-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/j178/prek/releases/download/v0.4.8/prek-i686-unknown-linux-gnu.tar.gz">prek-i686-unknown-linux-gnu.tar.gz</a></td>
<td>x86 Linux</td>
<td><a
href="https://github.com/j178/prek/releases/download/v0.4.8/prek-i686-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
</tbody>
</table>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/blob/master/CHANGELOG.md">prek's
changelog</a>.</em></p>
<blockquote>
<h2>0.4.8</h2>
<p>Released on 2026-07-04.</p>
<h3>Enhancements</h3>
<ul>
<li>Add default_env configuration (<a
href="https://redirect.github.com/j178/prek/pull/2288">#2288</a>)</li>
<li>Rename <code>auto-update</code> to <code>update</code> (<a
href="https://redirect.github.com/j178/prek/pull/2286">#2286</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Fix progress collapse ordering (<a
href="https://redirect.github.com/j178/prek/pull/2291">#2291</a>)</li>
<li>Fix progress insertion after collapsed rows (<a
href="https://redirect.github.com/j178/prek/pull/2292">#2292</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/j178"><code>@​j178</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/j178/prek/commit/cccc61bef9310b818a27aaeed1c25ef9917b09f5"><code>cccc61b</code></a>
Bump version to 0.4.8</li>
<li><a
href="https://github.com/j178/prek/commit/1ff8e408d7024d14a7892d233a22f9ee0dc893e9"><code>1ff8e40</code></a>
Bump version to 0.4.7 (<a
href="https://redirect.github.com/j178/prek/issues/2294">#2294</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/29bfccef3e12389f00e9836c1a43b31c07787bdd"><code>29bfcce</code></a>
Add default_env configuration (<a
href="https://redirect.github.com/j178/prek/issues/2288">#2288</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/5f0e654a0ef044f6a6d8f50b075abb919818fb41"><code>5f0e654</code></a>
Fix progress insertion after collapsed rows (<a
href="https://redirect.github.com/j178/prek/issues/2292">#2292</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/ce4363d33e6ade4f3f8a46902193703204ad91f8"><code>ce4363d</code></a>
Fix progress collapse ordering (<a
href="https://redirect.github.com/j178/prek/issues/2291">#2291</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/3f41d4c21593f3983dd479d21e0308e5cd602b26"><code>3f41d4c</code></a>
Remove Goldziher/kreuzberg from README (<a
href="https://redirect.github.com/j178/prek/issues/2290">#2290</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/57493f93a0eb51d2a87d334c97952d1182975a6d"><code>57493f9</code></a>
Delete <code>.github/copilot-instructions.md</code> (<a
href="https://redirect.github.com/j178/prek/issues/2287">#2287</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/ec18e36cf57318d9400d56701609f0df28f106d6"><code>ec18e36</code></a>
Rename <code>auto-update</code> to <code>update</code> (<a
href="https://redirect.github.com/j178/prek/issues/2286">#2286</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/3c0e172d114e653ec67ee4b16b8e5c1b999f1428"><code>3c0e172</code></a>
Migrate to tombi for TOML formatting (<a
href="https://redirect.github.com/j178/prek/issues/2285">#2285</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/a40cd329457bc489735a34619c65c9f0bbc10fcf"><code>a40cd32</code></a>
ci: rely on setup-node npm for npm publishing (<a
href="https://redirect.github.com/j178/prek/issues/2284">#2284</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/j178/prek/compare/v0.4.6...v0.4.8">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-08 02:00:54 -04:00
dependabot[bot] c487c6c483 Bump seleniumbase from 4.50.4 to 4.50.5 in the python-deps group (#1106)
Bumps the python-deps group with 1 update:
[seleniumbase](https://github.com/seleniumbase/SeleniumBase).

Updates `seleniumbase` from 4.50.4 to 4.50.5
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.50.5 - Fix Brave and Opera automation</h2>
<h2>Fix Brave and Opera automation</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/c41948eab2d4e8a981e783042bf2f49ef4079de7">Fix
Brave Browser automation</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/2632525f3b16313cc00cd6c068cf22dfbbce7be6">Fix
Opera Browser automation</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/308e71fca644ea7a896f17e4b65d77e4c807a0f6">Refresh
Python dependencies</a></li>
</ul>
<h3>More info:</h3>
<ul>
<li>The <code>--test-type</code> arg started breaking Brave on version
150. It's no longer used if Brave is set.</li>
<li>Brave with regular Chromedriver stopped worked. Since it works with
UC, that mode is forced.</li>
<li>Opera Browser now needs specific versions of Chromedriver and UC.
(Those versions are set.)</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>Fix Brave and Opera automation by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4418">seleniumbase/SeleniumBase#4418</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.50.4...v4.50.5">https://github.com/seleniumbase/SeleniumBase/compare/v4.50.4...v4.50.5</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/4de63c89d1815c146f675efe1ceab5f5922f7ae9"><code>4de63c8</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4418">#4418</a>
from seleniumbase/fix-brave-and-opera</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/f4353ef73c5ef1de18e48f6f3380c2c777b38049"><code>f4353ef</code></a>
Version 4.50.5</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/308e71fca644ea7a896f17e4b65d77e4c807a0f6"><code>308e71f</code></a>
Refresh Python dependencies</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/2632525f3b16313cc00cd6c068cf22dfbbce7be6"><code>2632525</code></a>
Fix Opera Browser automation</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/c41948eab2d4e8a981e783042bf2f49ef4079de7"><code>c41948e</code></a>
Fix Brave Browser automation</li>
<li>See full diff in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.50.4...v4.50.5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=seleniumbase&package-manager=uv&previous-version=4.50.4&new-version=4.50.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-07 00:54:08 -04:00
CaliBrain 404e8cc5c5 path timeouts (#1103)
- Add configurable completed-path wait for external clients : - Add a
configurable Advanced setting, DOWNLOAD_CLIENT_COMPLETED_PATH_TIMEOUT,
for how long Shelfmark waits after a torrent or usenet client reports
completion before treating the completed path as missing. - Keep the
default at the existing 60-second grace period, with a maximum of 3600
seconds
- Add e2e testing
- Add e2e testing

Should fix #861
2026-07-06 01:51:58 -04:00
Alex SchittkoandCaliBrain 40d6a179b7 feat: native WireGuard VPN egress mode (USING_WIREGUARD) (#1097)
Add an opt-in WireGuard egress path alongside the existing Tor mode

---------

Co-authored-by: CaliBrain <calibrain@l4n.xyz>
2026-07-06 01:51:18 -04:00
CaliBrain 02a45c1a12 fix: pin chromium to 149.0.7827.196 — Debian's 150 breaks the CDP bypasser (#1105) 2026-07-06 01:33:10 -04:00
dependabot[bot] 4c78a2f3a0 Bump the python-deps group with 2 updates (#1104)
Bumps the python-deps group with 2 updates:
[seleniumbase](https://github.com/seleniumbase/SeleniumBase) and
[prek](https://github.com/j178/prek).

Updates `seleniumbase` from 4.50.3 to 4.50.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.50.4 - Updates for SOCKS5h support and PDF downloads</h2>
<h2>Updates for SOCKS5h support and PDF downloads</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/0125a8dfa4cf8187377a68cb094bd1aa400244c0">Add
support for &quot;socks5h&quot; proxies without auth</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/7c937ed4a760522ca0d546ae859c016f1e098343">Update
how PDF downloads are handled</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/9ac19fbb520957429e062d952b8863210469dd3a">Update
console scripts</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/0a5c44d1b6026b115c2ef257004465a21a9d6ea7">Refresh
optional Python dependencies</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/a022ada3f823bd6058074ef99da47f12fd218202">Update
documentation and comments</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/0b076173e998697661b8d57abaa95ee52c1b6760">Update
examples</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>Updates for SOCKS5h support and PDF downloads by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4415">seleniumbase/SeleniumBase#4415</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.50.3...v4.50.4">https://github.com/seleniumbase/SeleniumBase/compare/v4.50.3...v4.50.4</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/e6a60b8f15771117811226e85920542d33bfa519"><code>e6a60b8</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4415">#4415</a>
from seleniumbase/socks5h-and-pdf-download-updates</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/17d71ef57a31a4306d2e117278e8988c47a42783"><code>17d71ef</code></a>
Version 4.50.4</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/0b076173e998697661b8d57abaa95ee52c1b6760"><code>0b07617</code></a>
Update examples</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/a022ada3f823bd6058074ef99da47f12fd218202"><code>a022ada</code></a>
Update documentation and comments</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/0a5c44d1b6026b115c2ef257004465a21a9d6ea7"><code>0a5c44d</code></a>
Refresh optional Python dependencies</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/9ac19fbb520957429e062d952b8863210469dd3a"><code>9ac19fb</code></a>
Update console scripts</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/7c937ed4a760522ca0d546ae859c016f1e098343"><code>7c937ed</code></a>
Update how PDF downloads are handled</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/0125a8dfa4cf8187377a68cb094bd1aa400244c0"><code>0125a8d</code></a>
Add support for &quot;socks5h&quot; proxies without auth</li>
<li>See full diff in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.50.3...v4.50.4">compare
view</a></li>
</ul>
</details>
<br />

Updates `prek` from 0.4.5 to 0.4.6
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/releases">prek's
releases</a>.</em></p>
<blockquote>
<h2>0.4.6</h2>
<h2>Release Notes</h2>
<p>Released on 2026-07-01.</p>
<h3>Enhancements</h3>
<ul>
<li>Verify managed toolchain downloads before installation (<a
href="https://redirect.github.com/j178/prek/pull/2229">#2229</a>)</li>
<li>Add <code>PREK_DOCKER_NO_INIT</code> to opt-out Docker
<code>--init</code> (<a
href="https://redirect.github.com/j178/prek/pull/2242">#2242</a>)</li>
<li>Improve subprocess error messages (<a
href="https://redirect.github.com/j178/prek/pull/2257">#2257</a>)</li>
<li>Split run concurrency knobs: <code>PREK_CONCURRENT_HOOKS</code> and
<code>PREK_CONCURRENT_BATCHES</code> (<a
href="https://redirect.github.com/j178/prek/pull/2276">#2276</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Avoid allocating markdown extensions per file (<a
href="https://redirect.github.com/j178/prek/pull/2245">#2245</a>)</li>
<li>Avoid unchanged config tracking writes (<a
href="https://redirect.github.com/j178/prek/pull/2247">#2247</a>)</li>
<li>Delay trailing whitespace output buffer (<a
href="https://redirect.github.com/j178/prek/pull/2244">#2244</a>)</li>
<li>Prefilter VCS permalink lines (<a
href="https://redirect.github.com/j178/prek/pull/2253">#2253</a>)</li>
<li>Skip unnecessary submodule updates during hook repo clone (<a
href="https://redirect.github.com/j178/prek/pull/2255">#2255</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Document <code>language_version</code> inferring for Python and Go
(<a
href="https://redirect.github.com/j178/prek/pull/2241">#2241</a>)</li>
</ul>
<h3>Other changes</h3>
<ul>
<li>Refactor env var handling (<a
href="https://redirect.github.com/j178/prek/pull/2277">#2277</a>)</li>
<li>Use <code>serde-saphyr</code> for YAML string quoting (<a
href="https://redirect.github.com/j178/prek/pull/2228">#2228</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/shaanmajid"><code>@​shaanmajid</code></a></li>
<li><a href="https://github.com/j178"><code>@​j178</code></a></li>
</ul>
<h2>Install prek 0.4.6</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://github.com/j178/prek/releases/download/v0.4.6/prek-installer.sh
| sh
</code></pre>
<h3>Install prebuilt binaries via powershell script</h3>
<pre lang="sh"><code>powershell -ExecutionPolicy Bypass -c &quot;irm
https://github.com/j178/prek/releases/download/v0.4.6/prek-installer.ps1
| iex&quot;
</code></pre>
<h3>Install prebuilt binaries via Homebrew</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/blob/master/CHANGELOG.md">prek's
changelog</a>.</em></p>
<blockquote>
<h2>0.4.6</h2>
<p>Released on 2026-07-01.</p>
<h3>Enhancements</h3>
<ul>
<li>Verify managed toolchain downloads before installation (<a
href="https://redirect.github.com/j178/prek/pull/2229">#2229</a>)</li>
<li>Add <code>PREK_DOCKER_NO_INIT</code> to opt-out Docker
<code>--init</code> (<a
href="https://redirect.github.com/j178/prek/pull/2242">#2242</a>)</li>
<li>Improve subprocess error messages (<a
href="https://redirect.github.com/j178/prek/pull/2257">#2257</a>)</li>
<li>Split run concurrency knobs: <code>PREK_CONCURRENT_HOOKS</code> and
<code>PREK_CONCURRENT_BATCHES</code> (<a
href="https://redirect.github.com/j178/prek/pull/2276">#2276</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Avoid allocating markdown extensions per file (<a
href="https://redirect.github.com/j178/prek/pull/2245">#2245</a>)</li>
<li>Avoid unchanged config tracking writes (<a
href="https://redirect.github.com/j178/prek/pull/2247">#2247</a>)</li>
<li>Delay trailing whitespace output buffer (<a
href="https://redirect.github.com/j178/prek/pull/2244">#2244</a>)</li>
<li>Prefilter VCS permalink lines (<a
href="https://redirect.github.com/j178/prek/pull/2253">#2253</a>)</li>
<li>Skip unnecessary submodule updates during hook repo clone (<a
href="https://redirect.github.com/j178/prek/pull/2255">#2255</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Document <code>language_version</code> inferring for Python and Go
(<a
href="https://redirect.github.com/j178/prek/pull/2241">#2241</a>)</li>
</ul>
<h3>Other changes</h3>
<ul>
<li>Refactor env var handling (<a
href="https://redirect.github.com/j178/prek/pull/2277">#2277</a>)</li>
<li>Use <code>serde-saphyr</code> for YAML string quoting (<a
href="https://redirect.github.com/j178/prek/pull/2228">#2228</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/shaanmajid"><code>@​shaanmajid</code></a></li>
<li><a href="https://github.com/j178"><code>@​j178</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/j178/prek/commit/643fba48aac2bdff4993e4d0b58c765a49df3188"><code>643fba4</code></a>
Bump version to 0.4.6 (<a
href="https://redirect.github.com/j178/prek/issues/2280">#2280</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/b0f92611f3b26c1436b98ba95678b1c95e2ac749"><code>b0f9261</code></a>
Refactor env var handling (<a
href="https://redirect.github.com/j178/prek/issues/2277">#2277</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/8494d1663e2c330e03ac0944bd6f81e8028f7a8a"><code>8494d16</code></a>
Split run concurrency knobs (<a
href="https://redirect.github.com/j178/prek/issues/2276">#2276</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/f20bbaaedaf95ebcdda053de1548b2c24e20eb55"><code>f20bbaa</code></a>
Update sample config for <code>.pre-commit-config.yaml</code> (<a
href="https://redirect.github.com/j178/prek/issues/2275">#2275</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/5485289875b75170fce1ef3ed6c8964bd48fe91c"><code>5485289</code></a>
Pin pre-commit repos to commit hash (<a
href="https://redirect.github.com/j178/prek/issues/2274">#2274</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/d418ab322bb05539409c6a90a6c898c46a6b96a1"><code>d418ab3</code></a>
Improve nix shebang parsing (<a
href="https://redirect.github.com/j178/prek/issues/2271">#2271</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/cc261c13a0a9d7e10e6a713a9d9fe81594073f75"><code>cc261c1</code></a>
Update actions/checkout action to v7 (<a
href="https://redirect.github.com/j178/prek/issues/2267">#2267</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/8d2dbba355f8cca46c6a1f2386f1505db13d6053"><code>8d2dbba</code></a>
Update Rust crate itertools to 0.15.0 (<a
href="https://redirect.github.com/j178/prek/issues/2265">#2265</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/51db86cd2ae4dd496e7a7f5ea94c5013d4e5ac1f"><code>51db86c</code></a>
Update Rust crate phf to 0.14.0 (<a
href="https://redirect.github.com/j178/prek/issues/2266">#2266</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/1aa7d389e8e9d782e11bb08a7223858557e56875"><code>1aa7d38</code></a>
Update dependency uv to v0.11.23 (<a
href="https://redirect.github.com/j178/prek/issues/2262">#2262</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/j178/prek/compare/v0.4.5...v0.4.6">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-06 00:32:57 -04:00
Alex Schittko 8b4230ee9f fix(bypass): resolve Xlib namespace collision breaking CDP browser startup (#1099)
## Problem

The full (browser) image ships two conflicting distributions that both
own the top-level `Xlib/` namespace:

- **`python-xlib` 0.33** (2022) — pulled by the `browser` extra
- **`python3-xlib` 0.15** (2014) — pulled transitively via `pyautogui` →
`mouseinfo`

Both install into the same `Xlib/` directory, so install order decides
which files survive. When the 2014 `python3-xlib` lands last, `Xlib.X`
is missing the `FamilyServerInterpreted` attribute that the SeleniumBase
Pure-CDP driver references during browser startup. Every bypass attempt
then fails with:

```
Pure CDP browser startup failed: module 'Xlib.X' has no attribute 'FamilyServerInterpreted'
Bypasser error: RuntimeError: Pure CDP browser startup failed: module 'Xlib.X' has no attribute 'FamilyServerInterpreted'
```

Result: **all Cloudflare / DDoS-Guard protected downloads fail** on
affected builds.

## Fix

After the browser stack is installed, uninstall the stale `python3-xlib`
and force-reinstall `python-xlib==0.33` so it deterministically owns the
namespace. A build-time assertion checks
`Xlib.X.FamilyServerInterpreted` exists so the image fails fast if the
collision ever returns.

`pyautogui` operates correctly against `python-xlib` 0.33 (superset
API), so nothing else regresses.

## Testing

- Reproduced live: a build where `python3-xlib` won produced the exact
`FamilyServerInterpreted` error on every CDP browser start; removing it
and reinstating `python-xlib==0.33` immediately restored `Chrome browser
ready (Pure CDP)` and successful `_bypass_method_cdp_solve`.
- The new build-time assertion prints `Xlib namespace OK: (0, 33)` and
exits non-zero if the attribute is ever missing.
2026-07-04 09:50:33 -04:00
dependabot[bot] b57925c39d Bump the python-deps group across 1 directory with 3 updates (#1098)
Bumps the python-deps group with 3 updates in the / directory:
[qbittorrent-api](https://github.com/rmartin16/qbittorrent-api),
[seleniumbase](https://github.com/seleniumbase/SeleniumBase) and
[basedpyright](https://github.com/detachhead/basedpyright).

Updates `qbittorrent-api` from 2026.6.0 to 2026.6.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/rmartin16/qbittorrent-api/releases">qbittorrent-api's
releases</a>.</em></p>
<blockquote>
<h2>release-2026.6.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Bump the github-actions group with 4 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/rmartin16/qbittorrent-api/pull/629">rmartin16/qbittorrent-api#629</a></li>
<li>fix: correct copy-paste docstring error in rss_move_item by <a
href="https://github.com/AmSach"><code>@​AmSach</code></a> in <a
href="https://redirect.github.com/rmartin16/qbittorrent-api/pull/628">rmartin16/qbittorrent-api#628</a></li>
<li>Bump starlette from 1.2.1 to 1.3.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/rmartin16/qbittorrent-api/pull/630">rmartin16/qbittorrent-api#630</a></li>
<li>Bump cryptography from 48.0.0 to 48.0.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/rmartin16/qbittorrent-api/pull/631">rmartin16/qbittorrent-api#631</a></li>
<li>qBittorrent 5.2.2 support by <a
href="https://github.com/agrenott"><code>@​agrenott</code></a> in <a
href="https://redirect.github.com/rmartin16/qbittorrent-api/pull/632">rmartin16/qbittorrent-api#632</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/agrenott"><code>@​agrenott</code></a>
made their first contribution in <a
href="https://redirect.github.com/rmartin16/qbittorrent-api/pull/632">rmartin16/qbittorrent-api#632</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/rmartin16/qbittorrent-api/compare/v2026.6.0...v2026.6.1">https://github.com/rmartin16/qbittorrent-api/compare/v2026.6.0...v2026.6.1</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/rmartin16/qbittorrent-api/blob/main/CHANGELOG.md">qbittorrent-api's
changelog</a>.</em></p>
<blockquote>
<h3>v2026.6.1 (26 jun 2026)</h3>
<ul>
<li>Advertise support for qBittorrent v5.2.2</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/5985973705f7b0c4df4c5d1f7decba792973a2d5"><code>5985973</code></a>
bump to v2026.6.1</li>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/89c1206bcf115343d2548e5a2e61e5445c2d35bd"><code>89c1206</code></a>
qBittorrent 5.2.2 support (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/632">#632</a>)</li>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/491cc3003f648e6ba5e617c974fa97ce4c1316a8"><code>491cc30</code></a>
Bump cryptography from 48.0.0 to 48.0.1 (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/631">#631</a>)</li>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/605814a55478a5f6f7b1631986787fd818a85057"><code>605814a</code></a>
Bump starlette from 1.2.1 to 1.3.1 (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/630">#630</a>)</li>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/d1ed4487f66fe122bd8e885633d03f80892e2977"><code>d1ed448</code></a>
fix: correct copy-paste docstring error in rss_move_item (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/628">#628</a>)</li>
<li><a
href="https://github.com/rmartin16/qbittorrent-api/commit/56f3d2981d95db3323ab68edcdbe8d71e85412d9"><code>56f3d29</code></a>
Bump the github-actions group with 4 updates (<a
href="https://redirect.github.com/rmartin16/qbittorrent-api/issues/629">#629</a>)</li>
<li>See full diff in <a
href="https://github.com/rmartin16/qbittorrent-api/compare/v2026.6.0...v2026.6.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `seleniumbase` from 4.50.2 to 4.50.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.50.3 - CDP Mode: Patch 118</h2>
<h2>CDP Mode: Patch 118</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/cc9050e60e1f57d7178e216182f1f6e02bdc2b16">Update
CDP Mode</a>
-- Simplifies <code>--use-chromium</code> to just
<code>--chromium</code> in command-line options
-- Use the mock-keychain when setting a custom Chromium binary location
-- Adjusts the DataDome Slider CAPTCHA-solving algorithm</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/7e168b767efb81ff645a38d17097ce7c174bd6a2">Update
CDP Mode examples</a>
-- Use <code>asyncio.run()</code> instead of
<code>loop.run_until_complete()</code> as needed</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>CDP Mode: Patch 118 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4412">seleniumbase/SeleniumBase#4412</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.50.2...v4.50.3">https://github.com/seleniumbase/SeleniumBase/compare/v4.50.2...v4.50.3</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/d2e98d47d0f3ee72464a4bb9bba0d6347ea9e34e"><code>d2e98d4</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4412">#4412</a>
from seleniumbase/cdp-mode-patch-118</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/092b0f475fb7d19ae9a5d3de16b81a0cf886adcf"><code>092b0f4</code></a>
Version 4.50.3</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/f57e0662a5fd8601c75e9ab1b5eb2a51e9947c78"><code>f57e066</code></a>
Update the documentation</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/7e168b767efb81ff645a38d17097ce7c174bd6a2"><code>7e168b7</code></a>
Update CDP Mode examples</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/cc9050e60e1f57d7178e216182f1f6e02bdc2b16"><code>cc9050e</code></a>
Update CDP Mode</li>
<li>See full diff in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.50.2...v4.50.3">compare
view</a></li>
</ul>
</details>
<br />

Updates `basedpyright` from 1.39.8 to 1.39.9
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/2802042079937529ad9f343d00a035c24e767065"><code>2802042</code></a>
fix <code>pnpm view</code> command and simplify the outcome check</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/f6e62d3550648052ebce7ccfe75e90072c2fa44a"><code>f6e62d3</code></a>
fix crash during publish</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/816614ebb5fb21303ba6df0edc299b0235e9ebf6"><code>816614e</code></a>
1.39.9</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/c79697f782786d759d2848be3994b72561863191"><code>c79697f</code></a>
remove more webpack remnants</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/2921109c27b043cda1af67521fd6b2a37373a6f6"><code>2921109</code></a>
<a
href="https://redirect.github.com/microsoft/vscode-vsce/issues/421">microsoft/vscode-vsce#421</a></li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/58120740bf2b100672830d031f23dd171b10be19"><code>5812074</code></a>
fix new test from upstream. that representation of a paramspec is wrong,
espe...</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/eaaaf62a90dce1a7c275304835f5c103cd50b4c8"><code>eaaaf62</code></a>
prettier ignore new files from upstream</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/8baf13e358c905ab1a2e0395dfec40b030889e84"><code>8baf13e</code></a>
switch to pnpm because npm is the worst package manager ever made.</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/f228a802fd022367201207e062610b44bc33fd21"><code>f228a80</code></a>
fixes from merge</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/21702747ca61358441bef00e50b39451af37537d"><code>2170274</code></a>
Merge tag '1.1.411' into merge-1.1.411</li>
<li>Additional commits viewable in <a
href="https://github.com/detachhead/basedpyright/compare/v1.39.8...v1.39.9">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-04 09:47:15 -04:00
dependabot[bot] b6efed95ab Bump the gh-actions group with 2 updates (#1102)
Bumps the gh-actions group with 2 updates:
[actions/attest-build-provenance](https://github.com/actions/attest-build-provenance)
and [dorny/paths-filter](https://github.com/dorny/paths-filter).

Updates `actions/attest-build-provenance` from 4.1.0 to 4.1.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/attest-build-provenance/releases">actions/attest-build-provenance's
releases</a>.</em></p>
<blockquote>
<h2>v4.1.1</h2>
<blockquote>
<p>[!NOTE]
As of version 4, <code>actions/attest-build-provenance</code> is simply
a wrapper on top of <a
href="https://github.com/actions/attest"><code>actions/attest</code></a>.</p>
<p>Existing applications may continue to use the
<code>attest-build-provenance</code> action, but new implementations
should use <code>actions/attest</code> instead.</p>
</blockquote>
<h2>What's Changed</h2>
<ul>
<li>Update actions/attest to v4.1.1 by <a
href="https://github.com/bdehamer"><code>@​bdehamer</code></a> in <a
href="https://redirect.github.com/actions/attest-build-provenance/pull/858">actions/attest-build-provenance#858</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/attest-build-provenance/compare/v4.1.0...v4.1.1">https://github.com/actions/attest-build-provenance/compare/v4.1.0...v4.1.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/attest-build-provenance/commit/0f67c3f4856b2e3261c31976d6725780e5e4c373"><code>0f67c3f</code></a>
Bump actions/checkout from 6.0.3 to 7.0.0 (<a
href="https://redirect.github.com/actions/attest-build-provenance/issues/857">#857</a>)</li>
<li><a
href="https://github.com/actions/attest-build-provenance/commit/21b787db45d5d4b6fc776b99a247a6493082d1fd"><code>21b787d</code></a>
Update actions/attest to v4.1.1 (<a
href="https://redirect.github.com/actions/attest-build-provenance/issues/858">#858</a>)</li>
<li><a
href="https://github.com/actions/attest-build-provenance/commit/f14352a605c57b0a66e0d49bba5825967f1dbedf"><code>f14352a</code></a>
add dependabot cooldown (<a
href="https://redirect.github.com/actions/attest-build-provenance/issues/851">#851</a>)</li>
<li><a
href="https://github.com/actions/attest-build-provenance/commit/2c04a00460b64b4dfd062384e60ea87fa18e1728"><code>2c04a00</code></a>
Bump actions/checkout from 6.0.2 to 6.0.3 in the actions-minor group (<a
href="https://redirect.github.com/actions/attest-build-provenance/issues/850">#850</a>)</li>
<li><a
href="https://github.com/actions/attest-build-provenance/commit/10334b5f1e684784025c3fc0a277c88c19089275"><code>10334b5</code></a>
remove badges from README (<a
href="https://redirect.github.com/actions/attest-build-provenance/issues/840">#840</a>)</li>
<li><a
href="https://github.com/actions/attest-build-provenance/commit/c5efebd311e9a809e832398ca2b73bb429ff3508"><code>c5efebd</code></a>
remove prober workflows (<a
href="https://redirect.github.com/actions/attest-build-provenance/issues/837">#837</a>)</li>
<li>See full diff in <a
href="https://github.com/actions/attest-build-provenance/compare/a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32...0f67c3f4856b2e3261c31976d6725780e5e4c373">compare
view</a></li>
</ul>
</details>
<br />

Updates `dorny/paths-filter` from 4 to 4.0.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/dorny/paths-filter/releases">dorny/paths-filter's
releases</a>.</em></p>
<blockquote>
<h2>v4.0.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Support merge queue by <a
href="https://github.com/masaru-iritani"><code>@​masaru-iritani</code></a>
in <a
href="https://redirect.github.com/dorny/paths-filter/pull/255">dorny/paths-filter#255</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/masaru-iritani"><code>@​masaru-iritani</code></a>
made their first contribution in <a
href="https://redirect.github.com/dorny/paths-filter/pull/255">dorny/paths-filter#255</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/dorny/paths-filter/compare/v4.0.0...v4.0.1">https://github.com/dorny/paths-filter/compare/v4.0.0...v4.0.1</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md">dorny/paths-filter's
changelog</a>.</em></p>
<blockquote>
<h2>v4.0.1</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/255">Support
merge queue</a></li>
</ul>
<h2>v4.0.0</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/294">Update
action runtime to node24</a></li>
</ul>
<h2>v3.0.3</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/279">Add
missing predicate-quantifier</a></li>
</ul>
<h2>v3.0.2</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/224">Add
config parameter for predicate quantifier</a></li>
</ul>
<h2>v3.0.1</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/133">Compare
base and ref when token is empty</a></li>
</ul>
<h2>v3.0.0</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/210">Update to
Node.js 20</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/215">Update
all dependencies</a></li>
</ul>
<h2>v2.11.1</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/167">Update
@​actions/core to v1.10.0 - Fixes warning about deprecated
set-output</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/168">Document
need for pull-requests: read permission</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/164">Updating
to actions/checkout@v3</a></li>
</ul>
<h2>v2.11.0</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/157">Set
list-files input parameter as not required</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/161">Update
Node.js</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/162">Fix
incorrect handling of Unicode characters in exec()</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/163">Use
Octokit pagination</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/160">Updates
real world links</a></li>
</ul>
<h2>v2.10.2</h2>
<ul>
<li><a href="https://redirect.github.com/dorny/paths-filter/pull/91">Fix
getLocalRef() returns wrong ref</a></li>
</ul>
<h2>v2.10.1</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/85">Improve
robustness of change detection</a></li>
</ul>
<h2>v2.10.0</h2>
<ul>
<li><a href="https://redirect.github.com/dorny/paths-filter/pull/82">Add
ref input parameter</a></li>
<li><a href="https://redirect.github.com/dorny/paths-filter/pull/83">Fix
change detection in PR when pullRequest.changed_files is
incorrect</a></li>
</ul>
<h2>v2.9.3</h2>
<ul>
<li><a href="https://redirect.github.com/dorny/paths-filter/pull/78">Fix
change detection when base is a tag</a></li>
</ul>
<h2>v2.9.2</h2>
<ul>
<li><a href="https://redirect.github.com/dorny/paths-filter/pull/75">Fix
fetching git history</a></li>
</ul>
<h2>v2.9.1</h2>
<ul>
<li><a href="https://redirect.github.com/dorny/paths-filter/pull/74">Fix
fetching git history + fallback to unshallow repo</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/dorny/paths-filter/commit/3c49e64ca26115121162fb767bc6af9e8d059f1a"><code>3c49e64</code></a>
Merge pull request <a
href="https://redirect.github.com/dorny/paths-filter/issues/210">#210</a>
from dorny/use-nodejs-20</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/8ec7be473424d724f09d3c51b7962ae5f2ffcbab"><code>8ec7be4</code></a>
Update to nodejs 20</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/100a1198b209450115509c0565d407ac269190dc"><code>100a119</code></a>
Revert back to node16</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/96be2b61c476f7b821cc2ed4eb7cbe4d96d30f60"><code>96be2b6</code></a>
Merge pull request <a
href="https://redirect.github.com/dorny/paths-filter/issues/206">#206</a>
from danielhjacobs/patch-1</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/f5071954e88595af02cdf15a1cb0c0c22fc8ca08"><code>f507195</code></a>
Update Node.js to version 20</li>
<li>See full diff in <a
href="https://github.com/dorny/paths-filter/compare/v4...v4.0.1">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-04 09:47:01 -04:00
dependabot[bot] 44a261e857 Bump the npm-deps group in /src/frontend with 9 updates (#1101)
Bumps the npm-deps group in /src/frontend with 9 updates:

| Package | From | To |
| --- | --- | --- |
|
[@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite)
| `4.3.1` | `4.3.2` |
|
[react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom)
| `7.18.0` | `7.18.1` |
|
[@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)
| `26.0.0` | `26.0.1` |
|
[@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react)
| `6.0.2` | `6.0.3` |
| [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) |
`6.18.0` | `6.23.0` |
| [oxfmt](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt) |
`0.56.0` | `0.57.0` |
| [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) |
`1.71.0` | `1.72.0` |
|
[tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss)
| `4.3.1` | `4.3.2` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) |
`8.0.16` | `8.1.0` |

Updates `@tailwindcss/vite` from 4.3.1 to 4.3.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tailwindlabs/tailwindcss/releases">@​tailwindcss/vite's
releases</a>.</em></p>
<blockquote>
<h2>v4.3.2</h2>
<h3>Fixed</h3>
<ul>
<li>Support bare spacing values for <code>auto-rows-*</code> and
<code>auto-cols-*</code> utilities (e.g. <code>auto-rows-12</code> and
<code>auto-cols-16</code>) (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20229">#20229</a>)</li>
<li>Prevent <code>@tailwindcss/cli</code> in <code>--watch</code> mode
from crashing on Windows when <code>@source</code> points to a directory
that doesn't exist (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20242">#20242</a>)</li>
<li>Prevent <code>@tailwindcss/vite</code> from crashing in Deno v2.8.x
when <code>context.parentURL</code> is not a valid URL (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20245">#20245</a>)</li>
<li>Ensure <code>@tailwindcss/cli</code> in <code>--watch</code> mode
rebuilds when the input CSS file changes in an ignored directory (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20246">#20246</a>)</li>
<li>Allow <code>@variant</code> rules used in <code>addBase(…)</code> to
use custom variants defined later (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20247">#20247</a>)</li>
<li>Prevent <code>@tailwindcss/vite</code> from crashing during HMR when
scanned files or directories are deleted (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20259">#20259</a>)</li>
<li>Generate <code>font-size</code> instead of <code>color</code>
declarations for <code>text-[--spacing(…)]</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20260">#20260</a>)</li>
<li>Prevent <code>@source</code> patterns from scanning unrelated
sibling files and folders (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20263">#20263</a>)</li>
<li>Extract class candidates adjacent to Template Toolkit delimiters
like <code>%]…[%</code> in <code>.tt</code>, <code>.tt2</code>, and
<code>.tx</code> files (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20269">#20269</a>)</li>
<li>Extract class candidates from conditional Maud syntax like
<code>p.text-black[condition]</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20269">#20269</a>)</li>
<li>Prevent <code>@position-try</code> rules from triggering unknown
at-rule warnings when optimizing CSS (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20277">#20277</a>)</li>
<li>Support class suggestions for named opacity modifiers from
<code>--opacity</code> theme values (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20287">#20287</a>)</li>
<li>Prevent type errors in <code>@tailwindcss/postcss</code> when used
with newer PostCSS patch releases (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20289">#20289</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md">@​tailwindcss/vite's
changelog</a>.</em></p>
<blockquote>
<h2>[4.3.2] - 2026-06-26</h2>
<h3>Fixed</h3>
<ul>
<li>Support bare spacing values for <code>auto-rows-*</code> and
<code>auto-cols-*</code> utilities (e.g. <code>auto-rows-12</code> and
<code>auto-cols-16</code>) (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20229">#20229</a>)</li>
<li>Prevent <code>@tailwindcss/cli</code> in <code>--watch</code> mode
from crashing on Windows when <code>@source</code> points to a directory
that doesn't exist (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20242">#20242</a>)</li>
<li>Prevent <code>@tailwindcss/vite</code> from crashing in Deno v2.8.x
when <code>context.parentURL</code> is not a valid URL (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20245">#20245</a>)</li>
<li>Ensure <code>@tailwindcss/cli</code> in <code>--watch</code> mode
rebuilds when the input CSS file changes in an ignored directory (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20246">#20246</a>)</li>
<li>Allow <code>@variant</code> rules used in <code>addBase(…)</code> to
use custom variants defined later (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20247">#20247</a>)</li>
<li>Prevent <code>@tailwindcss/vite</code> from crashing during HMR when
scanned files or directories are deleted (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20259">#20259</a>)</li>
<li>Generate <code>font-size</code> instead of <code>color</code>
declarations for <code>text-[--spacing(…)]</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20260">#20260</a>)</li>
<li>Prevent <code>@source</code> patterns from scanning unrelated
sibling files and folders (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20263">#20263</a>)</li>
<li>Extract class candidates adjacent to Template Toolkit delimiters
like <code>%]…[%</code> in <code>.tt</code>, <code>.tt2</code>, and
<code>.tx</code> files (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20269">#20269</a>)</li>
<li>Extract class candidates from conditional Maud syntax like
<code>p.text-black[condition]</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20269">#20269</a>)</li>
<li>Prevent <code>@position-try</code> rules from triggering unknown
at-rule warnings when optimizing CSS (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20277">#20277</a>)</li>
<li>Support class suggestions for named opacity modifiers from
<code>--opacity</code> theme values (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20287">#20287</a>)</li>
<li>Prevent type errors in <code>@tailwindcss/postcss</code> when used
with newer PostCSS patch releases (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20289">#20289</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/056a1550721d4bf79ff732d5ab9414fa83f7064f"><code>056a155</code></a>
4.3.2 (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite/issues/20281">#20281</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/bb6a10937c7e1434db39919fbb4df4a8982dba7f"><code>bb6a109</code></a>
use <code>.ts</code> instead of <code>.css</code></li>
<li>See full diff in <a
href="https://github.com/tailwindlabs/tailwindcss/commits/v4.3.2/packages/@tailwindcss-vite">compare
view</a></li>
</ul>
</details>
<br />

Updates `react-router-dom` from 7.18.0 to 7.18.1
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/remix-run/react-router/blob/react-router-dom@7.18.1/packages/react-router-dom/CHANGELOG.md">react-router-dom's
changelog</a>.</em></p>
<blockquote>
<h2>v7.18.1</h2>
<h3>Patch Changes</h3>
<ul>
<li>Fix incorrect <code>package.json</code> <code>main</code> field for
CommonJS builds (<a
href="https://redirect.github.com/remix-run/react-router/pull/15238">#15238</a>)</li>
<li>Updated dependencies:
<ul>
<li><a
href="https://github.com/remix-run/react-router/releases/tag/react-router@7.18.1"><code>react-router@7.18.1</code></a></li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/remix-run/react-router/commit/afdf85d3c15448a41017514caca2aca038d3e9ca"><code>afdf85d</code></a>
Release v7.18.1 (<a
href="https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom/issues/15253">#15253</a>)</li>
<li><a
href="https://github.com/remix-run/react-router/commit/2ecaa1ddbbcd583999dda46dd5413e907e8a46f3"><code>2ecaa1d</code></a>
Fix react-router-dom main entry metadata (<a
href="https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom/issues/15238">#15238</a>)</li>
<li>See full diff in <a
href="https://github.com/remix-run/react-router/commits/react-router-dom@7.18.1/packages/react-router-dom">compare
view</a></li>
</ul>
</details>
<br />

Updates `@types/node` from 26.0.0 to 26.0.1
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node">compare
view</a></li>
</ul>
</details>
<br />

Updates `@vitejs/plugin-react` from 6.0.2 to 6.0.3
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md">@​vitejs/plugin-react's
changelog</a>.</em></p>
<blockquote>
<h2>6.0.3 (2026-06-23)</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/640fd358a0e82393acfce4e92e19a6ac6e1641a7"><code>640fd35</code></a>
release: plugin-react@6.0.3</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/889efb02cdc4ec978a5e177a37e0213cfded38a4"><code>889efb0</code></a>
fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1249">#1249</a>)</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/6c57dd4c5d71075b48039df2532804e72880da21"><code>6c57dd4</code></a>
fix(plugin-react): use '/' base in bundledDev preamble to fix non-root
base p...</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/3cc33a703636b558a1c1c99e787ddc6bd64aab2d"><code>3cc33a7</code></a>
fix(deps): update react-related dependencies (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1245">#1245</a>)</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/c0f7c7ff709dc9d88bc1f29f1b27c1b3e2bfcfca"><code>c0f7c7f</code></a>
docs: mention the Biome rule in the &quot;Consistent components
exports&quot; section (...</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/cd80f0f7b2b750f6e8f719f9c3dbe4f22ddd94db"><code>cd80f0f</code></a>
fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1241">#1241</a>)</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/e38accafea0c7c84f7fc72fd69d9cec731fa7600"><code>e38acca</code></a>
fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/1227">#1227</a>)</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/9a9bb26c23b966dceed47ff9ec257faeb0e777d9"><code>9a9bb26</code></a>
perf(react): improve react compiler preset so that slightly more modules
are ...</li>
<li>See full diff in <a
href="https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.0.3/packages/plugin-react">compare
view</a></li>
</ul>
</details>
<br />

Updates `knip` from 6.18.0 to 6.23.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/webpro-nl/knip/releases">knip's
releases</a>.</em></p>
<blockquote>
<h2>Release 6.23.0</h2>
<ul>
<li>feat: add customCss to Starlight plugin (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1828">#1828</a>)
(f85d96f84a47f10c34df95a5246ee1ddefd95db5) - thanks <a
href="https://github.com/trueberryless"><code>@​trueberryless</code></a>!</li>
<li>fix: enable vite and vitest plugins when vite-plus is found (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1830">#1830</a>)
(62e97538fca8dff3d152326b114ffc4b7241a0d2) - thanks <a
href="https://github.com/ghostdevv"><code>@​ghostdevv</code></a>!</li>
<li>feat: add support for <code>@​astrojs/markdoc</code> (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1829">#1829</a>)
(94e2863308947f19f5e759cc12666952c8f683d7) - thanks <a
href="https://github.com/trueberryless"><code>@​trueberryless</code></a>!</li>
<li>Support nub (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1831">#1831</a>)
(8a6050e6a92da81d4875f730f852fb7d9252a018)</li>
<li>Don't report optimizeDeps and dedupe deps as unlisted (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1832">#1832</a>)
(849b5ac230e7a8c103b6e1b1e2ddb333d2da3ca0)</li>
</ul>
<h2>Release 6.22.0</h2>
<ul>
<li>Support XO v1+ (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1819">#1819</a>)
(1dffe368b5c336d190e358ab4c2e2240e3d50e26) - thanks <a
href="https://github.com/patrik-csak"><code>@​patrik-csak</code></a>!</li>
<li>feat: detect execaNode scripts in execa visitor (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1824">#1824</a>)
(5095ae1ccd0ceb083d4434e827443f03ba19a1ff) - thanks <a
href="https://github.com/gwagjiug"><code>@​gwagjiug</code></a>!</li>
<li>Skip optional peerDeps referenced only via a host (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1823">#1823</a>)
(7759a9894f2ac1d9425cf682e1e3c400f0976080)</li>
<li>docs: update npmjs.com links to npmx.dev (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1826">#1826</a>)
(11fe8bd248c839c6eeb95b06c8204c25294e0adb) - thanks <a
href="https://github.com/serhalp"><code>@​serhalp</code></a>!</li>
<li>docs: fix semi-broken link to DEVELOPMENT.md in CONTRIBUTING.md (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1827">#1827</a>)
(a5302b2466be1294633b1f40e86ca81a00605293) - thanks <a
href="https://github.com/serhalp"><code>@​serhalp</code></a>!</li>
<li>feat: add support for Lunaria (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1825">#1825</a>)
(3e1b8212086dfae26fa7f368f245285ec82af14d) - thanks <a
href="https://github.com/trueberryless"><code>@​trueberryless</code></a>!</li>
<li>Fix lint issues (76c92e2328a94257afead6ae497a747a9e2944ea)</li>
</ul>
<h2>Release 6.21.0</h2>
<ul>
<li>Detect Vite config dependencies (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1721">#1721</a>)
(8754c43368112922c6f80d1f8d1d8ddb6cb29f25)</li>
<li>fix: Update timerifyMethods to include resolveFromAST (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1814">#1814</a>)
(3c8deac3b856def31c16372f85525bf867105132) - thanks <a
href="https://github.com/gwagjiug"><code>@​gwagjiug</code></a>!</li>
<li>Fix crash on null root export in package.json (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1815">#1815</a>)
(9b8af2b343e1aacae46fedcb155252f56f9bae61)</li>
<li>Fix unresolved subpath imports with a colon prefix (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1816">#1816</a>)
(f89db4192ff7ff6c873828ed19fe40d379566b49)</li>
<li>Detect Next.js entry files in subdirectory (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1817">#1817</a>)
(f32c6ea215dde54a59af7b91fd8bdd2177cc2881)</li>
</ul>
<h2>Release 6.20.0</h2>
<ul>
<li>Add raw transfer opt-out (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1813">#1813</a>)
(6f08c680ac4acd6edf0806ba3c1c5c8f7bca24cd)</li>
<li>Fix cached plugin config cycles (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1811">#1811</a>)
(2bc2f2420ca71db1ae70846626c6152896d270ee)</li>
</ul>
<h2>Release 6.19.0</h2>
<ul>
<li>feat: support new optional sveltekit config pattern via vite config
(<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1810">#1810</a>)
(3fee8bf608e0862d7bcdd1377cfb859a9185f17d) - thanks <a
href="https://github.com/fubits1"><code>@​fubits1</code></a>!</li>
<li>Optimize hot path string scanning
(e30cfe796423e3ecd9adff42291f3c4de6604d2b)</li>
<li>Update astro snapshot
(71e71a71b888a1b8034d4438635e94831d62b330)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/webpro-nl/knip/commit/a802601072b0e3c98a20b6aaea4a4c555fbd6a55"><code>a802601</code></a>
Release knip@6.23.0</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/849b5ac230e7a8c103b6e1b1e2ddb333d2da3ca0"><code>849b5ac</code></a>
Don't report optimizeDeps and dedupe deps as unlisted (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1832">#1832</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/8a6050e6a92da81d4875f730f852fb7d9252a018"><code>8a6050e</code></a>
Support nub (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1831">#1831</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/94e2863308947f19f5e759cc12666952c8f683d7"><code>94e2863</code></a>
feat: add support for <code>@​astrojs/markdoc</code> (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1829">#1829</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/62e97538fca8dff3d152326b114ffc4b7241a0d2"><code>62e9753</code></a>
fix: enable vite and vitest plugins when vite-plus is found (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1830">#1830</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/f85d96f84a47f10c34df95a5246ee1ddefd95db5"><code>f85d96f</code></a>
feat: add customCss to Starlight plugin (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1828">#1828</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/c0537556aa6a99ae0fa48cf743798f02dde239eb"><code>c053755</code></a>
Release knip@6.22.0</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/76c92e2328a94257afead6ae497a747a9e2944ea"><code>76c92e2</code></a>
Fix lint issues</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/3e1b8212086dfae26fa7f368f245285ec82af14d"><code>3e1b821</code></a>
feat: add support for Lunaria (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1825">#1825</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/11fe8bd248c839c6eeb95b06c8204c25294e0adb"><code>11fe8bd</code></a>
docs: update npmjs.com links to npmx.dev (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1826">#1826</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/webpro-nl/knip/commits/knip@6.23.0/packages/knip">compare
view</a></li>
</ul>
</details>
<br />

Updates `oxfmt` from 0.56.0 to 0.57.0
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/commit/5306f24d9e82ae36ad9c3c964f33075bc589c799"><code>5306f24</code></a>
release(apps): oxlint v1.72.0 &amp;&amp; oxfmt v0.57.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/23935">#23935</a>)</li>
<li>See full diff in <a
href="https://github.com/oxc-project/oxc/commits/oxfmt_v0.57.0/npm/oxfmt">compare
view</a></li>
</ul>
</details>
<br />

Updates `oxlint` from 1.71.0 to 1.72.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/releases">oxlint's
releases</a>.</em></p>
<blockquote>
<h2>oxlint v1.27.0 &amp;&amp; oxfmt v0.12.0</h2>
<h1>Oxlint v1.27.0</h1>
<h3>🚀 Features</h3>
<ul>
<li>222a8f0 linter/plugins: Implement
<code>SourceCode#isSpaceBetween</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15498">#15498</a>)
(overlookmotel)</li>
<li>2f9735d linter/plugins: Implement
<code>context.languageOptions</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15486">#15486</a>)
(overlookmotel)</li>
<li>bc731ff linter/plugins: Stub out all <code>Context</code> APIs (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15479">#15479</a>)
(overlookmotel)</li>
<li>5822cb4 linter/plugins: Add <code>extend</code> method to
<code>FILE_CONTEXT</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15477">#15477</a>)
(overlookmotel)</li>
<li>7b1e6f3 apps: Add pure rust binaries and release to github (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15469">#15469</a>)
(Boshen)</li>
<li>2a89b43 linter: Introduce debug assertions after fixes to assert
validity (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15389">#15389</a>)
(camc314)</li>
<li>ad3c45a editor: Add <code>oxc.path.node</code> option (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15040">#15040</a>)
(Sysix)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>6f3cd77 linter/no-var: Incorrect warning for blocks (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15504">#15504</a>)
(Hamir Mahal)</li>
<li>6957fb9 linter/plugins: Do not allow access to
<code>Context#id</code> in <code>createOnce</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15489">#15489</a>)
(overlookmotel)</li>
<li>7409630 linter/plugins: Allow access to <code>cwd</code> in
<code>createOnce</code> in ESLint interop mode (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15488">#15488</a>)
(overlookmotel)</li>
<li>732205e parser: Reject <code>using</code> / <code>await using</code>
in a switch <code>case</code> / <code>default</code> clause (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15225">#15225</a>)
(sapphi-red)</li>
<li>a17ca32 linter/plugins: Replace <code>Context</code> class (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15448">#15448</a>)
(overlookmotel)</li>
<li>ecf2f7b language_server: Fail gracefully when tsgolint executable
not found (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15436">#15436</a>)
(camc314)</li>
<li>3c8d3a7 lang-server: Improve logging in failure case for tsgolint
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15299">#15299</a>)
(camc314)</li>
<li>ef71410 linter: Use jsx if source type is JS in fix debug assertion
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15434">#15434</a>)
(camc314)</li>
<li>e32bbf6 linter/no-var: Handle TypeScript declare keyword in fixer
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15426">#15426</a>)
(camc314)</li>
<li>6565dbe linter/switch-case-braces: Skip comments when searching for
<code>:</code> token (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15425">#15425</a>)
(camc314)</li>
<li>85bd19a linter/prefer-class-fields: Insert value after type
annotation in fixer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15423">#15423</a>)
(camc314)</li>
<li>fde753e linter/plugins: Block access to
<code>context.settings</code> in <code>createOnce</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15394">#15394</a>)
(overlookmotel)</li>
<li>ddd9f9f linter/forward-ref-uses-ref: Dont suggest removing wrapper
in invalid positions (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15388">#15388</a>)
(camc314)</li>
<li>dac2a9c linter/no-template-curly-in-string: Remove fixer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15387">#15387</a>)
(camc314)</li>
<li>989b8e3 linter/no-var: Only fix to <code>const</code> if the var has
an initializer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15385">#15385</a>)
(camc314)</li>
<li>cc403f5 linter/plugins: Return empty object for unimplemented
parserServices (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15364">#15364</a>)
(magic-akari)</li>
</ul>
<h3>⚡ Performance</h3>
<ul>
<li>25d577e language_server: Start tools in parallel (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15500">#15500</a>)
(Sysix)</li>
<li>3c57291 linter/plugins: Optimize loops (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15449">#15449</a>)
(overlookmotel)</li>
<li>3166233 linter/plugins: Remove <code>Arc</code>s (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15431">#15431</a>)
(overlookmotel)</li>
<li>9de1322 linter/plugins: Lazily deserialize settings JSON (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15395">#15395</a>)
(overlookmotel)</li>
<li>3049ec2 linter/plugins: Optimize <code>deepFreezeSettings</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15392">#15392</a>)
(overlookmotel)</li>
<li>444ebfd linter/plugins: Use single object for
<code>parserServices</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15378">#15378</a>)
(overlookmotel)</li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>97d2104 linter: Update comment in lint.rs about default value for
tsconfig path (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15530">#15530</a>)
(Connor Shea)</li>
<li>2c6bd9e linter: Always refer as &quot;ES2015&quot; instead of
&quot;ES6&quot; (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15411">#15411</a>)
(sapphi-red)</li>
<li>a0c5203 linter/import/named: Update &quot;ES7&quot; comment in
examples (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15410">#15410</a>)
(sapphi-red)</li>
<li>3dc24b5 linter,minifier: Always refer as &quot;ES Modules&quot;
instead of &quot;ES6 Modules&quot; (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15409">#15409</a>)
(sapphi-red)</li>
<li>2ad77fb linter/no-this-before-super: Correct &quot;Why is this
bad?&quot; section (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15408">#15408</a>)
(sapphi-red)</li>
<li>57f0ce1 linter: Add backquotes where appropriate (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15407">#15407</a>)
(sapphi-red)</li>
</ul>
<h1>Oxfmt v0.12.0</h1>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md">oxlint's
changelog</a>.</em></p>
<blockquote>
<h2>[1.72.0] - 2026-06-29</h2>
<h3>🚀 Features</h3>
<ul>
<li>1c8f50c linter: Add schema for
<code>eslint/no-restricted-import</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23642">#23642</a>)
(Sysix)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>742be36 refactor/node/handle-callback-err: Reject invalid regex
config (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23740">#23740</a>)
(camc314)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/commit/5306f24d9e82ae36ad9c3c964f33075bc589c799"><code>5306f24</code></a>
release(apps): oxlint v1.72.0 &amp;&amp; oxfmt v0.57.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23935">#23935</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/742be3616fc8df27053b09bdf9ff166e16761838"><code>742be36</code></a>
fix(refactor/node/handle-callback-err): reject invalid regex config (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23740">#23740</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/1c8f50c67ff636b9d8193d3d7be1511a7960f25d"><code>1c8f50c</code></a>
feat(linter): add schema for <code>eslint/no-restricted-import</code>
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23642">#23642</a>)</li>
<li>See full diff in <a
href="https://github.com/oxc-project/oxc/commits/oxlint_v1.72.0/npm/oxlint">compare
view</a></li>
</ul>
</details>
<br />

Updates `tailwindcss` from 4.3.1 to 4.3.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tailwindlabs/tailwindcss/releases">tailwindcss's
releases</a>.</em></p>
<blockquote>
<h2>v4.3.2</h2>
<h3>Fixed</h3>
<ul>
<li>Support bare spacing values for <code>auto-rows-*</code> and
<code>auto-cols-*</code> utilities (e.g. <code>auto-rows-12</code> and
<code>auto-cols-16</code>) (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20229">#20229</a>)</li>
<li>Prevent <code>@tailwindcss/cli</code> in <code>--watch</code> mode
from crashing on Windows when <code>@source</code> points to a directory
that doesn't exist (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20242">#20242</a>)</li>
<li>Prevent <code>@tailwindcss/vite</code> from crashing in Deno v2.8.x
when <code>context.parentURL</code> is not a valid URL (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20245">#20245</a>)</li>
<li>Ensure <code>@tailwindcss/cli</code> in <code>--watch</code> mode
rebuilds when the input CSS file changes in an ignored directory (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20246">#20246</a>)</li>
<li>Allow <code>@variant</code> rules used in <code>addBase(…)</code> to
use custom variants defined later (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20247">#20247</a>)</li>
<li>Prevent <code>@tailwindcss/vite</code> from crashing during HMR when
scanned files or directories are deleted (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20259">#20259</a>)</li>
<li>Generate <code>font-size</code> instead of <code>color</code>
declarations for <code>text-[--spacing(…)]</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20260">#20260</a>)</li>
<li>Prevent <code>@source</code> patterns from scanning unrelated
sibling files and folders (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20263">#20263</a>)</li>
<li>Extract class candidates adjacent to Template Toolkit delimiters
like <code>%]…[%</code> in <code>.tt</code>, <code>.tt2</code>, and
<code>.tx</code> files (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20269">#20269</a>)</li>
<li>Extract class candidates from conditional Maud syntax like
<code>p.text-black[condition]</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20269">#20269</a>)</li>
<li>Prevent <code>@position-try</code> rules from triggering unknown
at-rule warnings when optimizing CSS (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20277">#20277</a>)</li>
<li>Support class suggestions for named opacity modifiers from
<code>--opacity</code> theme values (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20287">#20287</a>)</li>
<li>Prevent type errors in <code>@tailwindcss/postcss</code> when used
with newer PostCSS patch releases (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20289">#20289</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md">tailwindcss's
changelog</a>.</em></p>
<blockquote>
<h2>[4.3.2] - 2026-06-26</h2>
<h3>Fixed</h3>
<ul>
<li>Support bare spacing values for <code>auto-rows-*</code> and
<code>auto-cols-*</code> utilities (e.g. <code>auto-rows-12</code> and
<code>auto-cols-16</code>) (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20229">#20229</a>)</li>
<li>Prevent <code>@tailwindcss/cli</code> in <code>--watch</code> mode
from crashing on Windows when <code>@source</code> points to a directory
that doesn't exist (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20242">#20242</a>)</li>
<li>Prevent <code>@tailwindcss/vite</code> from crashing in Deno v2.8.x
when <code>context.parentURL</code> is not a valid URL (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20245">#20245</a>)</li>
<li>Ensure <code>@tailwindcss/cli</code> in <code>--watch</code> mode
rebuilds when the input CSS file changes in an ignored directory (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20246">#20246</a>)</li>
<li>Allow <code>@variant</code> rules used in <code>addBase(…)</code> to
use custom variants defined later (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20247">#20247</a>)</li>
<li>Prevent <code>@tailwindcss/vite</code> from crashing during HMR when
scanned files or directories are deleted (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20259">#20259</a>)</li>
<li>Generate <code>font-size</code> instead of <code>color</code>
declarations for <code>text-[--spacing(…)]</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20260">#20260</a>)</li>
<li>Prevent <code>@source</code> patterns from scanning unrelated
sibling files and folders (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20263">#20263</a>)</li>
<li>Extract class candidates adjacent to Template Toolkit delimiters
like <code>%]…[%</code> in <code>.tt</code>, <code>.tt2</code>, and
<code>.tx</code> files (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20269">#20269</a>)</li>
<li>Extract class candidates from conditional Maud syntax like
<code>p.text-black[condition]</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20269">#20269</a>)</li>
<li>Prevent <code>@position-try</code> rules from triggering unknown
at-rule warnings when optimizing CSS (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20277">#20277</a>)</li>
<li>Support class suggestions for named opacity modifiers from
<code>--opacity</code> theme values (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20287">#20287</a>)</li>
<li>Prevent type errors in <code>@tailwindcss/postcss</code> when used
with newer PostCSS patch releases (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20289">#20289</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/056a1550721d4bf79ff732d5ab9414fa83f7064f"><code>056a155</code></a>
4.3.2 (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20281">#20281</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/c8b081d96376871acb0157923e894b7a929cad5b"><code>c8b081d</code></a>
Add suggestions for named opacity modifiers (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20287">#20287</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/c46f654fa0039ad4741c6ecefbe4e2a208a665c3"><code>c46f654</code></a>
Ensure <code>--alpha(…)</code> is seen as a <code>color</code>, and
<code>--spacing(…)</code> is seen as a `le...</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/5e9f66e4284164c62e80d74e69e0cbd8d9c645cc"><code>5e9f66e</code></a>
Ensure <code>@variant</code> can be used in JS based APIs (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20252">#20252</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/707c23b955bdfa7854a03195e503b95dd730b3d2"><code>707c23b</code></a>
Ensure custom variants can be used via <code>@variant</code> in
<code>addBase</code> (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20247">#20247</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/127d17033a0518701dac8332667b12e0980870ec"><code>127d170</code></a>
Add bare value support for <code>auto-rows-*</code> and
<code>auto-cols-*</code> (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20229">#20229</a>)</li>
<li>See full diff in <a
href="https://github.com/tailwindlabs/tailwindcss/commits/v4.3.2/packages/tailwindcss">compare
view</a></li>
</ul>
</details>
<br />

Updates `vite` from 8.0.16 to 8.1.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/releases">vite's
releases</a>.</em></p>
<blockquote>
<h2>create-vite@8.1.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/create-vite@8.1.0/packages/create-vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>plugin-legacy@8.1.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/plugin-legacy@8.1.0/packages/plugin-legacy/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v8.1.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v8.1.0/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>plugin-legacy@8.1.0-beta.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/plugin-legacy@8.1.0-beta.0/packages/plugin-legacy/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v8.1.0-beta.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v8.1.0-beta.0/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md">vite's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/vitejs/vite/compare/v8.1.0-beta.0...v8.1.0">8.1.0</a>
(2026-06-23)</h2>
<h3>Features</h3>
<ul>
<li>extend <code>server.fs.deny</code> list with common files (<a
href="https://redirect.github.com/vitejs/vite/issues/22707">#22707</a>)
(<a
href="https://github.com/vitejs/vite/commit/61ba8fdc6500e8aa668021833ae5e6ebd200466d">61ba8fd</a>)</li>
<li>update rolldown to 1.1.2 (<a
href="https://redirect.github.com/vitejs/vite/issues/22695">#22695</a>)
(<a
href="https://github.com/vitejs/vite/commit/4f008a6213014fe5e898f42561bdc621e103903e">4f008a6</a>)</li>
<li>use <code>~</code> for Rolldown (<a
href="https://redirect.github.com/vitejs/vite/issues/22693">#22693</a>)
(<a
href="https://github.com/vitejs/vite/commit/9928722f7d08de27907c48b65ddc071244166e05">9928722</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>bundled-dev:</strong> errors should be kept when incremental
build fails (<a
href="https://redirect.github.com/vitejs/vite/issues/22617">#22617</a>)
(<a
href="https://github.com/vitejs/vite/commit/9a0dd481ac2160078b8173879e0fa86e5e6af05d">9a0dd48</a>)</li>
<li>cache falsy values in perEnvironmentState (<a
href="https://redirect.github.com/vitejs/vite/issues/22715">#22715</a>)
(<a
href="https://github.com/vitejs/vite/commit/0e91e79841b501d29dc86a6b7c0313660781196a">0e91e79</a>)</li>
<li><strong>glob:</strong> respect caseSensitive option in hmr matcher
(<a
href="https://redirect.github.com/vitejs/vite/issues/22711">#22711</a>)
(<a
href="https://github.com/vitejs/vite/commit/65f525e643ba31faeaeafbc5d888b61465e6b48d">65f525e</a>)</li>
<li><strong>html:</strong> omit nonce on import map when cspNonce is
unset (<a
href="https://redirect.github.com/vitejs/vite/issues/22713">#22713</a>)
(<a
href="https://github.com/vitejs/vite/commit/8340bb51cab703427a13d5df40b0005352c1b676">8340bb5</a>)</li>
<li><strong>optimizer:</strong> skip null-valued exports in
expandGlobIds glob resolution (<a
href="https://redirect.github.com/vitejs/vite/issues/22611">#22611</a>)
(<a
href="https://github.com/vitejs/vite/commit/8b9f5cd9f19f6002e1323b10155ab943219e86aa">8b9f5cd</a>)</li>
<li>resolved build options should be kept as a getter (<a
href="https://redirect.github.com/vitejs/vite/issues/22691">#22691</a>)
(<a
href="https://github.com/vitejs/vite/commit/3527191cad6c935a3d129506cc9a893c84009037">3527191</a>)</li>
<li><strong>server:</strong> handle malformed URI in memory files
middleware (<a
href="https://redirect.github.com/vitejs/vite/issues/22714">#22714</a>)
(<a
href="https://github.com/vitejs/vite/commit/df9e0a589ff64e09c32beca9494bff4757d4ea37">df9e0a5</a>)</li>
<li>use literal envPrefix queries for Vite Task (<a
href="https://redirect.github.com/vitejs/vite/issues/22706">#22706</a>)
(<a
href="https://github.com/vitejs/vite/commit/da727337b13ac6144e523eb06852b2a52c377f22">da72733</a>)</li>
<li>warn on deprecated envFile (<a
href="https://redirect.github.com/vitejs/vite/issues/22555">#22555</a>)
(<a
href="https://github.com/vitejs/vite/commit/ed7b28352e347dc7c95d590d2e696c3e5bd1e3d7">ed7b283</a>)</li>
</ul>
<h3>Code Refactoring</h3>
<ul>
<li><strong>client:</strong> inline dev-id value in CSS selector (<a
href="https://redirect.github.com/vitejs/vite/issues/22736">#22736</a>)
(<a
href="https://github.com/vitejs/vite/commit/57f59bc843dd527a81c503684b7f408872edaef3">57f59bc</a>)</li>
<li>remove unused removeRawQuery util (<a
href="https://redirect.github.com/vitejs/vite/issues/22724">#22724</a>)
(<a
href="https://github.com/vitejs/vite/commit/403cc608e4e8633b0335f5e8221c83dfca4994f8">403cc60</a>)</li>
<li>use <code>rolldownOptions</code> property for chunkImportMap (<a
href="https://redirect.github.com/vitejs/vite/issues/22692">#22692</a>)
(<a
href="https://github.com/vitejs/vite/commit/8e8816ca9fa249d210150c5590bac06af3560aa7">8e8816c</a>)</li>
</ul>
<h2><a
href="https://github.com/vitejs/vite/compare/v8.0.16...v8.1.0-beta.0">8.1.0-beta.0</a>
(2026-06-15)</h2>
<h3>Features</h3>
<ul>
<li><code>import.meta.glob</code> support <code>caseSensitive</code>
option (<a
href="https://redirect.github.com/vitejs/vite/issues/21707">#21707</a>)
(<a
href="https://github.com/vitejs/vite/commit/2ad6737a838cc23ebb9b2766682d28b11078dab5">2ad6737</a>)</li>
<li>add warning to discourage Vite with yarn pnp (<a
href="https://redirect.github.com/vitejs/vite/issues/21906">#21906</a>)
(<a
href="https://github.com/vitejs/vite/commit/3fbb55add62a1b94a2b47b7da70db4ae99a7c943">3fbb55a</a>)</li>
<li><strong>build:</strong> chunk importmap (<a
href="https://redirect.github.com/vitejs/vite/issues/21580">#21580</a>)
(<a
href="https://github.com/vitejs/vite/commit/e1803128e5698202e22c6e5e17dc9ebd83d4f3f5">e180312</a>)</li>
<li><strong>css:</strong> support lightningcss plugin dependency (<a
href="https://redirect.github.com/vitejs/vite/issues/21748">#21748</a>)
(<a
href="https://github.com/vitejs/vite/commit/0b7aaedc98cacb00e6db33f5f79a70192da38304">0b7aaed</a>)</li>
<li><strong>deps:</strong> bump <code>@​vitejs/devtools</code> peer
dependency version (<a
href="https://redirect.github.com/vitejs/vite/issues/22542">#22542</a>)
(<a
href="https://github.com/vitejs/vite/commit/d2c2bc090bab9bb9508e5060accb5179b6310257">d2c2bc0</a>)</li>
<li><strong>html:</strong> add <code>html.additionalAssetSources</code>
option (<a
href="https://redirect.github.com/vitejs/vite/issues/21412">#21412</a>)
(<a
href="https://github.com/vitejs/vite/commit/a41404b8894d4de729ed49fb51e0b6230d6cb511">a41404b</a>)</li>
<li>integrate with Vite Task for zero-config build caching (<a
href="https://redirect.github.com/vitejs/vite/issues/22453">#22453</a>)
(<a
href="https://github.com/vitejs/vite/commit/f8d75f7723aeb635126c8dc0e0c33266c72d24c0">f8d75f7</a>)</li>
<li>rename <code>server.hmr</code> options to <code>server.ws</code>
options (<a
href="https://redirect.github.com/vitejs/vite/issues/21357">#21357</a>)
(<a
href="https://github.com/vitejs/vite/commit/9ce3036d31d4e56b8ef3730a39a7f0e54baa951f">9ce3036</a>)</li>
<li><strong>server:</strong> support multiple hosts in
__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS (<a
href="https://redirect.github.com/vitejs/vite/issues/21501">#21501</a>)
(<a
href="https://github.com/vitejs/vite/commit/735f9a19a9d0e313c865c87daad677aaf358406b">735f9a1</a>)</li>
<li>track dependencies when loading config with native (<a
href="https://redirect.github.com/vitejs/vite/issues/22602">#22602</a>)
(<a
href="https://github.com/vitejs/vite/commit/a7e2da8cdb306cfdf1660aa4b1fe24873a8dd873">a7e2da8</a>)</li>
<li><strong>types:</strong> add more precise typing for known
<code>query</code> types to match known <code>as</code> types (<a
href="https://redirect.github.com/vitejs/vite/issues/21863">#21863</a>)
(<a
href="https://github.com/vitejs/vite/commit/cc39e55401d559b771fa3e029d90c419a364b805">cc39e55</a>)</li>
<li>update rolldown to 1.1.1 (<a
href="https://redirect.github.com/vitejs/vite/issues/22593">#22593</a>)
(<a
href="https://github.com/vitejs/vite/commit/8a13d6337eea3e74d6a7abaaa7e1d378f5b9bc49">8a13d63</a>)</li>
<li><strong>wasm:</strong> direct .wasm imports (WASM ESM Integration)
(<a
href="https://redirect.github.com/vitejs/vite/issues/21779">#21779</a>)
(<a
href="https://github.com/vitejs/vite/commit/c23d85b9654685d7217cfc33817d2953b77744f8">c23d85b</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>apply correct fs restrictions for pnpm gvs (<a
href="https://redirect.github.com/vitejs/vite/issues/22415">#22415</a>)
(<a
href="https://github.com/vitejs/vite/commit/092320bfe115e4d9952a7d172bc40cd3ec14a4c3">092320b</a>)</li>
<li><strong>css:</strong> support external CSS with lightningcss (<a
href="https://redirect.github.com/vitejs/vite/issues/18389">#18389</a>)
(<a
href="https://github.com/vitejs/vite/commit/d64a1a5557b3caea9469e70b647ff2c9d9def809">d64a1a5</a>)</li>
<li><strong>deps:</strong> update all non-major dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/22637">#22637</a>)
(<a
href="https://github.com/vitejs/vite/commit/44bb9d95a246011a2fba9a19cadeac4181bef1b4">44bb9d9</a>)</li>
<li><strong>deps:</strong> update all non-major dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/22681">#22681</a>)
(<a
href="https://github.com/vitejs/vite/commit/f4f063382dde0d67b2158374c56443d0bf5c9501">f4f0633</a>)</li>
<li><strong>html:</strong> insert import map before modulepreload that
is not self-close tag (<a
href="https://redirect.github.com/vitejs/vite/issues/21409">#21409</a>)
(<a
href="https://github.com/vitejs/vite/commit/e399c89a7decff73ff42a4a01679ecc235ce6845">e399c89</a>)</li>
<li><strong>optimizer:</strong> preserve sourcemaps for transformed
optimized deps with follow-up transforms (<a
href="https://redirect.github.com/vitejs/vite/issues/22428">#22428</a>)
(<a
href="https://github.com/vitejs/vite/commit/1298951ebc5e5a94164c21f142fe748ca37eea68">1298951</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/vitejs/vite/commit/5909efd8fbfd1bf1eab65427aea0613124b2797a"><code>5909efd</code></a>
fix: allow multiple <code>bindCLIShortcuts</code> calls with shortcut
merging (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21103">#21103</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/39a0a15fd24ed37257c48b795097a3794e54d255"><code>39a0a15</code></a>
chore(deps): update rolldown-related dependencies (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21095">#21095</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/6a34ac3422686e7cf7cc9a25d299cb8e5a8d92a0"><code>6a34ac3</code></a>
fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21096">#21096</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/02ceaec45e17bef19159188a28d9196fed1761be"><code>02ceaec</code></a>
chore(deps): update dependency <code>@​rollup/plugin-commonjs</code> to
v29 (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21099">#21099</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/572aaca4594ed2377a4f63443a449e4bf3c0468c"><code>572aaca</code></a>
release: v7.2.2</li>
<li><a
href="https://github.com/vitejs/vite/commit/728c8eeebc0ad7ba48e680f46bbdb55020b2e152"><code>728c8ee</code></a>
fix: revert &quot;refactor: use fs.cpSync (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21019">#21019</a>)&quot;
(<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21081">#21081</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/a532e68c6b7ed82ae7569d2d3380eb6417e9267d"><code>a532e68</code></a>
release: v7.2.1</li>
<li><a
href="https://github.com/vitejs/vite/commit/82d2d6ccf5a69c3d67e44e3704a0c3eb34db2236"><code>82d2d6c</code></a>
fix(worker): some worker asset was missing (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21074">#21074</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/f83264f5d97caa8ba80276a9ee9e82cb0e69c4ca"><code>f83264f</code></a>
refactor(build): rename <code>indexOfMatchInSlice</code> to
<code>findPreloadMarker</code> (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21054">#21054</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/8293de0e17af8a876d49761ed6651bd38b709174"><code>8293de0</code></a>
release: v7.2.0</li>
<li>Additional commits viewable in <a
href="https://github.com/vitejs/vite/commits/create-vite@8.1.0/packages/vite">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-04 09:46:54 -04:00
dependabot[bot] 14bd50bfd1 Bump python from 63a4c7f to b877e50 (#1100)
Bumps python from `63a4c7f` to `b877e50`.


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=python&package-manager=docker&previous-version=3.14.6-slim&new-version=3.14.6-slim)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-04 09:46:45 -04:00
dependabot[bot]andCaliBrain 4f123977ff Bump the npm-deps group in /src/frontend with 4 updates (#1091)
Bumps the npm-deps group in /src/frontend with 4 updates:
[react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom),
[knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip),
[oxfmt](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt) and
[oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint).

Updates `react-router-dom` from 7.17.0 to 7.18.0
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/remix-run/react-router/blob/react-router-dom@7.18.0/packages/react-router-dom/CHANGELOG.md">react-router-dom's
changelog</a>.</em></p>
<blockquote>
<h2>v7.18.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies:
<ul>
<li><a
href="https://github.com/remix-run/react-router/releases/tag/react-router@7.18.0"><code>react-router@7.18.0</code></a></li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/remix-run/react-router/commit/6fb1e79f8304eddd8b78759edea83cb32389ebf5"><code>6fb1e79</code></a>
Release v7.18.0 (<a
href="https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom/issues/15187">#15187</a>)</li>
<li>See full diff in <a
href="https://github.com/remix-run/react-router/commits/react-router-dom@7.18.0/packages/react-router-dom">compare
view</a></li>
</ul>
</details>
<br />

Updates `knip` from 6.17.0 to 6.18.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/webpro-nl/knip/releases">knip's
releases</a>.</em></p>
<blockquote>
<h2>Release 6.18.0</h2>
<ul>
<li>Update dependencies (pin oxc-resolver)
(7dda4eccc65c6d61ef2546442eb752b2a73edab9)</li>
<li>Resolve tsconfig paths for non-TS importers independent of oxc
ownership (3b71565e72107d43cbd6d2ddec7ab2fbbf65c001)</li>
<li>Format (64865f8247b8956def52f1a387234562fbddd667)</li>
<li>Fix false positive for Vitest mocks (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1802">#1802</a>)
(ec93e2013deb53902b406463b30fa6386445f9c9) - thanks <a
href="https://github.com/remcohaszing"><code>@​remcohaszing</code></a>!</li>
<li>Mark npx-run binaries optional unless --no-install (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1803">#1803</a>)
(203c31e1b2bd77eb9c94f82121f353fbf0671c67)</li>
<li>Ignore pnpm [WARN] lines in ecosystem snapshots
(392835a39b9429a3d85d712025da4c6531b8ece6)</li>
<li>Update slonik snapshot
(62d802bf8d53d7790f6322f481a290e09812cbcc)</li>
<li>Update Jest entry patterns for Jest 30 (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1808">#1808</a>)
(d2caeddf32ba99ca12e5c26e891b7974392f981a) - thanks <a
href="https://github.com/gwagjiug"><code>@​gwagjiug</code></a>!</li>
<li>Report stale workspaces configuration keys as configuration hints
(<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1807">#1807</a>)
(9083c16b3313fbebf5cb3cd11cadf45ac773bc3d) - thanks <a
href="https://github.com/WooWan"><code>@​WooWan</code></a>!</li>
</ul>
<h2>Release 6.17.2</h2>
<ul>
<li>Fix up jest plugin (63dbd653b6e1be08a36401f5f728b351ab69e81b)</li>
<li>Detect coverage provider from bare <code>vitest --coverage</code>
flag (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1800">#1800</a>)
(dc11d9fc5458e6e1f734013eb82403eab07af2c1) - thanks <a
href="https://github.com/WooWan"><code>@​WooWan</code></a>!</li>
<li>Don't disable configuration hints in workspace-scoped runs (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1791">#1791</a>)
(8ce1ec8160a786dad90903e43c3ef646ffba9464) - thanks <a
href="https://github.com/WooWan"><code>@​WooWan</code></a>!</li>
<li>Detect react-email v6 packages from non-numeric version ranges
(resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1798">#1798</a>)
(27a1caeb1bff6abcccd7140c7e92e4a57197ad47)</li>
<li>Discover workspaces included after a negated pattern (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1797">#1797</a>)
(630e152f6f687d9404cc25ffa01f0d49737d9229)</li>
</ul>
<h2>Release 6.17.1</h2>
<ul>
<li>Remove ignoreBinaries w/ tar
(b13d0ca1ce0a201b7e66f725039d9f346b4d424e)</li>
<li>Wrap up docs/refs (29f3e46cc765a70643bcf2ef96e940ff371f39c6)</li>
<li>Update dependencies (7b2f3458176110900204fa49e50650ac50d1f4db)</li>
<li>Fix up vscode-languageclient imports
(820c2335ca63f329f862eb8ec5c264bd8d5f09eb)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/webpro-nl/knip/commit/7bfd556b0217f3ee09fd953132bdef860417c44c"><code>7bfd556</code></a>
Release knip@6.18.0</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/9083c16b3313fbebf5cb3cd11cadf45ac773bc3d"><code>9083c16</code></a>
Report stale workspaces configuration keys as configuration hints (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1807">#1807</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/d2caeddf32ba99ca12e5c26e891b7974392f981a"><code>d2caedd</code></a>
Update Jest entry patterns for Jest 30 (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1808">#1808</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/203c31e1b2bd77eb9c94f82121f353fbf0671c67"><code>203c31e</code></a>
Mark npx-run binaries optional unless --no-install (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1803">#1803</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/ec93e2013deb53902b406463b30fa6386445f9c9"><code>ec93e20</code></a>
Fix false positive for Vitest mocks (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1802">#1802</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/64865f8247b8956def52f1a387234562fbddd667"><code>64865f8</code></a>
Format</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/3b71565e72107d43cbd6d2ddec7ab2fbbf65c001"><code>3b71565</code></a>
Resolve tsconfig paths for non-TS importers independent of oxc
ownership</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/7dda4eccc65c6d61ef2546442eb752b2a73edab9"><code>7dda4ec</code></a>
Update dependencies (pin oxc-resolver)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/419805396c10579b51a2576fe70971910ad58b55"><code>4198053</code></a>
Release knip@6.17.2</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/630e152f6f687d9404cc25ffa01f0d49737d9229"><code>630e152</code></a>
Discover workspaces included after a negated pattern (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1797">#1797</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/webpro-nl/knip/commits/knip@6.18.0/packages/knip">compare
view</a></li>
</ul>
</details>
<br />

Updates `oxfmt` from 0.55.0 to 0.56.0
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/blob/main/npm/oxfmt/CHANGELOG.md">oxfmt's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<p>All notable changes to this package will be documented in this
file.</p>
<p>The format is based on <a
href="https://keepachangelog.com/en/1.0.0">Keep a Changelog</a>.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/commit/c4be770f24adc4026efa96fb82182f743c55e423"><code>c4be770</code></a>
release(apps): oxlint v1.71.0 &amp;&amp; oxfmt v0.56.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/23707">#23707</a>)</li>
<li>See full diff in <a
href="https://github.com/oxc-project/oxc/commits/oxfmt_v0.56.0/npm/oxfmt">compare
view</a></li>
</ul>
</details>
<br />

Updates `oxlint` from 1.70.0 to 1.71.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/releases">oxlint's
releases</a>.</em></p>
<blockquote>
<h2>oxlint v1.27.0 &amp;&amp; oxfmt v0.12.0</h2>
<h1>Oxlint v1.27.0</h1>
<h3>🚀 Features</h3>
<ul>
<li>222a8f0 linter/plugins: Implement
<code>SourceCode#isSpaceBetween</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15498">#15498</a>)
(overlookmotel)</li>
<li>2f9735d linter/plugins: Implement
<code>context.languageOptions</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15486">#15486</a>)
(overlookmotel)</li>
<li>bc731ff linter/plugins: Stub out all <code>Context</code> APIs (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15479">#15479</a>)
(overlookmotel)</li>
<li>5822cb4 linter/plugins: Add <code>extend</code> method to
<code>FILE_CONTEXT</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15477">#15477</a>)
(overlookmotel)</li>
<li>7b1e6f3 apps: Add pure rust binaries and release to github (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15469">#15469</a>)
(Boshen)</li>
<li>2a89b43 linter: Introduce debug assertions after fixes to assert
validity (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15389">#15389</a>)
(camc314)</li>
<li>ad3c45a editor: Add <code>oxc.path.node</code> option (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15040">#15040</a>)
(Sysix)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>6f3cd77 linter/no-var: Incorrect warning for blocks (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15504">#15504</a>)
(Hamir Mahal)</li>
<li>6957fb9 linter/plugins: Do not allow access to
<code>Context#id</code> in <code>createOnce</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15489">#15489</a>)
(overlookmotel)</li>
<li>7409630 linter/plugins: Allow access to <code>cwd</code> in
<code>createOnce</code> in ESLint interop mode (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15488">#15488</a>)
(overlookmotel)</li>
<li>732205e parser: Reject <code>using</code> / <code>await using</code>
in a switch <code>case</code> / <code>default</code> clause (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15225">#15225</a>)
(sapphi-red)</li>
<li>a17ca32 linter/plugins: Replace <code>Context</code> class (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15448">#15448</a>)
(overlookmotel)</li>
<li>ecf2f7b language_server: Fail gracefully when tsgolint executable
not found (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15436">#15436</a>)
(camc314)</li>
<li>3c8d3a7 lang-server: Improve logging in failure case for tsgolint
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15299">#15299</a>)
(camc314)</li>
<li>ef71410 linter: Use jsx if source type is JS in fix debug assertion
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15434">#15434</a>)
(camc314)</li>
<li>e32bbf6 linter/no-var: Handle TypeScript declare keyword in fixer
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15426">#15426</a>)
(camc314)</li>
<li>6565dbe linter/switch-case-braces: Skip comments when searching for
<code>:</code> token (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15425">#15425</a>)
(camc314)</li>
<li>85bd19a linter/prefer-class-fields: Insert value after type
annotation in fixer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15423">#15423</a>)
(camc314)</li>
<li>fde753e linter/plugins: Block access to
<code>context.settings</code> in <code>createOnce</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15394">#15394</a>)
(overlookmotel)</li>
<li>ddd9f9f linter/forward-ref-uses-ref: Dont suggest removing wrapper
in invalid positions (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15388">#15388</a>)
(camc314)</li>
<li>dac2a9c linter/no-template-curly-in-string: Remove fixer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15387">#15387</a>)
(camc314)</li>
<li>989b8e3 linter/no-var: Only fix to <code>const</code> if the var has
an initializer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15385">#15385</a>)
(camc314)</li>
<li>cc403f5 linter/plugins: Return empty object for unimplemented
parserServices (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15364">#15364</a>)
(magic-akari)</li>
</ul>
<h3>⚡ Performance</h3>
<ul>
<li>25d577e language_server: Start tools in parallel (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15500">#15500</a>)
(Sysix)</li>
<li>3c57291 linter/plugins: Optimize loops (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15449">#15449</a>)
(overlookmotel)</li>
<li>3166233 linter/plugins: Remove <code>Arc</code>s (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15431">#15431</a>)
(overlookmotel)</li>
<li>9de1322 linter/plugins: Lazily deserialize settings JSON (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15395">#15395</a>)
(overlookmotel)</li>
<li>3049ec2 linter/plugins: Optimize <code>deepFreezeSettings</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15392">#15392</a>)
(overlookmotel)</li>
<li>444ebfd linter/plugins: Use single object for
<code>parserServices</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15378">#15378</a>)
(overlookmotel)</li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>97d2104 linter: Update comment in lint.rs about default value for
tsconfig path (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15530">#15530</a>)
(Connor Shea)</li>
<li>2c6bd9e linter: Always refer as &quot;ES2015&quot; instead of
&quot;ES6&quot; (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15411">#15411</a>)
(sapphi-red)</li>
<li>a0c5203 linter/import/named: Update &quot;ES7&quot; comment in
examples (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15410">#15410</a>)
(sapphi-red)</li>
<li>3dc24b5 linter,minifier: Always refer as &quot;ES Modules&quot;
instead of &quot;ES6 Modules&quot; (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15409">#15409</a>)
(sapphi-red)</li>
<li>2ad77fb linter/no-this-before-super: Correct &quot;Why is this
bad?&quot; section (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15408">#15408</a>)
(sapphi-red)</li>
<li>57f0ce1 linter: Add backquotes where appropriate (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15407">#15407</a>)
(sapphi-red)</li>
</ul>
<h1>Oxfmt v0.12.0</h1>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md">oxlint's
changelog</a>.</em></p>
<blockquote>
<h2>[1.71.0] - 2026-06-22</h2>
<h3>🚀 Features</h3>
<ul>
<li>0dc2405 linter: Add schema for
<code>eslint/no-restricted-properties</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23619">#23619</a>)
(Sysix)</li>
<li>b638d0e linter: Add schema for <code>node/callback-return</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23615">#23615</a>)
(Sysix)</li>
<li>eb8bedc linter: Add schema for <code>import/extensions</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23557">#23557</a>)
(WaterWhisperer)</li>
<li>46f3625 linter: Implement node/no-sync rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23589">#23589</a>)
(fujitani sora)</li>
<li>b01739a linter: Add schema for
<code>unicorn/numeric-separators-style</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23554">#23554</a>)
(Mikhail Baev)</li>
<li>68afd2a linter/node: Implement <code>no-mixed-requires</code> rule
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23539">#23539</a>)
(fujitani sora)</li>
<li>a421215 linter: Add schema for
<code>eslint/prefer-destructuring</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23410">#23410</a>)
(WaterWhisperer)</li>
<li>84438be linter/jsdoc: Added missing options to
<code>require-param-description</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23416">#23416</a>)
(kapobajza)</li>
<li>51910df linter/jsdoc: Add missing options to
<code>require-param-type</code> rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23418">#23418</a>)
(kapobajza)</li>
<li>e90925f linter/unicorn: Implement prefer-number-coercion rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23497">#23497</a>)
(Shekhu☺️)</li>
<li>dd1c866 linter/vue: Implement no-async-in-computed-properties rule
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23493">#23493</a>)
(bab)</li>
<li>b02444e linter: Add schema for <code>react/jsx-no-script-url</code>
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23475">#23475</a>)
(WaterWhisperer)</li>
<li>a8dce46 linter/unicorn: Implement <code>max-nested-calls</code> rule
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23461">#23461</a>)
(arieleli01212)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>a303c23 linter/jsx-a11y: Align <code>anchor-is-valid</code> config
with upstream (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23446">#23446</a>)
(camc314)</li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>b50bf4d linter: Remove manually written options doc for
<code>eslint/arrow-body-style</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23490">#23490</a>)
(Mikhail Baev)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/commit/c4be770f24adc4026efa96fb82182f743c55e423"><code>c4be770</code></a>
release(apps): oxlint v1.71.0 &amp;&amp; oxfmt v0.56.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23707">#23707</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/0dc24054119b22247c6a7b9bdb20cc6e589f267a"><code>0dc2405</code></a>
feat(linter): add schema for
<code>eslint/no-restricted-properties</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23619">#23619</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/b638d0ee1df63487e7c04e031b4b71ab10d592ce"><code>b638d0e</code></a>
feat(linter): add schema for <code>node/callback-return</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23615">#23615</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/6d355ab8b3644505c4491da5fc4ec88695de50f7"><code>6d355ab</code></a>
refactor(linter): remove <code>number_as_object_schema</code> helper (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23614">#23614</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/eb8bedc2fba9cead8632a75bb7936205fae7dcf0"><code>eb8bedc</code></a>
feat(linter): add schema for <code>import/extensions</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23557">#23557</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/46f362530da1c2c5e868613e339999314b9f3a72"><code>46f3625</code></a>
feat(linter): implement node/no-sync rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23589">#23589</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/953c7b3f1c9e2c80ee1b782f1540d277cc22ed99"><code>953c7b3</code></a>
refactor(linter): make <code>unicorn/numeric-separators-style</code>
options <code>u32</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23558">#23558</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/b01739ade376a9862fdb325ac9ef7023f504baf3"><code>b01739a</code></a>
feat(linter): add schema for
<code>unicorn/numeric-separators-style</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23554">#23554</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/68afd2a1f7c8d624b17c0e24880f684d3c7db3c5"><code>68afd2a</code></a>
feat(linter/node): implement <code>no-mixed-requires</code> rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23539">#23539</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/b08e9f5b9e9d948f873d199fb6317997cd296e11"><code>b08e9f5</code></a>
refactor(linter): re-enable schema for
`jsx_a11y/no-noninteractive-element-in...</li>
<li>Additional commits viewable in <a
href="https://github.com/oxc-project/oxc/commits/oxlint_v1.71.0/npm/oxlint">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: CaliBrain <calibrain@l4n.xyz>
2026-06-29 12:23:35 -04:00
dependabot[bot] e3b05d6030 Bump the python-deps group with 3 updates (#1094)
Bumps the python-deps group with 3 updates:
[seleniumbase](https://github.com/seleniumbase/SeleniumBase),
[pytest](https://github.com/pytest-dev/pytest) and
[ruff](https://github.com/astral-sh/ruff).

Updates `seleniumbase` from 4.50.1 to 4.50.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.50.2 - CDP Mode: Patch 117</h2>
<h2>CDP Mode: Patch 117</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/89858eaffdabcacec77b5357a60418b7a8e59c5e">Optimize
CDP Mode</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/e32858d3b6f0262570ffc81c13a387bd5f74865c">Update
Dashboard layout</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/12450caa378317d2673a8b375474c11959f21682">Update
CDP Mode examples</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/f2e9878ca10ba849221b0905023ed373990cfaf1">Refresh
Python dependencies</a></li>
</ul>
<p>This includes several improvements to CDP Mode:</p>
<ul>
<li>Fixes a bug where temporary user_data_dirs wasn't getting cleaned up
properly.</li>
<li>Fewer asyncio event loops used for browser processes. (Same one for
startup and usage.)</li>
<li>The painted <code>flash()</code> dot is no longer used everywhere
automatically. (Anti-bots started detecting it.)</li>
</ul>
<p>Other updates include:</p>
<ul>
<li>Refreshed Python dependencies.</li>
<li>A small change to Dashboard layout.</li>
<li>Updated examples due to website changes.</li>
</ul>
<p>Special thanks to <a
href="https://github.com/montovaneli"><code>@​montovaneli</code></a>.
(<a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4408">seleniumbase/SeleniumBase#4408</a>)</p>
<p>Changes are expected in version <code>4.50.2</code>.</p>
<h2>What's Changed</h2>
<ul>
<li>CDP Mode: Patch 117 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4409">seleniumbase/SeleniumBase#4409</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.50.1...v4.50.2">https://github.com/seleniumbase/SeleniumBase/compare/v4.50.1...v4.50.2</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/08a5e1b3bb7869425285453263e286a505111206"><code>08a5e1b</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4409">#4409</a>
from seleniumbase/cdp-mode-patch-117</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/e2fd3edce00bf7f38ef6ce0ce85322fe114ef5f6"><code>e2fd3ed</code></a>
Version 4.50.2</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/f2e9878ca10ba849221b0905023ed373990cfaf1"><code>f2e9878</code></a>
Refresh Python dependencies</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/12450caa378317d2673a8b375474c11959f21682"><code>12450ca</code></a>
Update CDP Mode examples</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/e32858d3b6f0262570ffc81c13a387bd5f74865c"><code>e32858d</code></a>
Update Dashboard layout</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/89858eaffdabcacec77b5357a60418b7a8e59c5e"><code>89858ea</code></a>
Optimize CDP Mode</li>
<li>See full diff in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.50.1...v4.50.2">compare
view</a></li>
</ul>
</details>
<br />

Updates `pytest` from 9.1.0 to 9.1.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pytest-dev/pytest/releases">pytest's
releases</a>.</em></p>
<blockquote>
<h2>9.1.1</h2>
<h1>pytest 9.1.1 (2026-06-19)</h1>
<h2>Bug fixes</h2>
<ul>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14220">#14220</a>:
Fixed a logic bug in <code>pytest.RaisesGroup</code> which would might
cause it to display incorrect &quot;It matches <!-- raw HTML omitted
-->FooError()<!-- raw HTML omitted --> which was paired with <!-- raw
HTML omitted -->BarError<!-- raw HTML omitted -->&quot; messages.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14591">#14591</a>:
Fixed a regression in pytest 9.1.0 which caused overriding a
parametrized fixture with an indirect <!-- raw HTML omitted --><a
href="https://github.com/pytest"><code>@​pytest</code></a>.mark.parametrize<!--
raw HTML omitted --> to fail with &quot;duplicate parametrization of
'&lt;fixture name&gt;'&quot;.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14606">#14606</a>:
Fixed <code>list-item</code> typing errors from mypy in
<code>@pytest.mark.parametrize &lt;pytest.mark.parametrize
ref&gt;</code> <code>argvalues</code> parameter.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14608">#14608</a>:
Fixed a regression in pytest 9.1.0 where <code>conftest.py</code> files
located in <code>&lt;invocation dir&gt;/test*</code> were no longer
loaded as initial conftests when invoked without arguments.
This could cause certain hooks (like <code>pytest_addoption</code>) in
these files to not fire.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pytest-dev/pytest/commit/cf470ec0bf7eb89cd97dd56df4859eae5db46447"><code>cf470ec</code></a>
Prepare release version 9.1.1</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/e0c8ce6cc5db1f08363be6f152c32e6838df2690"><code>e0c8ce6</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14625">#14625</a>
from pytest-dev/patchback/backports/9.1.x/a07c31a97...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/1b82d1694fce22385ee7a4287917fbafbaf2e757"><code>1b82d16</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14624">#14624</a>
from pytest-dev/patchback/backports/9.1.x/b375b79ec...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/501c4bc784da3b08bfcaa64858eba5d15dc59e53"><code>501c4bc</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14596">#14596</a>
from bluetech/doc-classmethod</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/b61f588e36e9377c3d1d3f06bece1da0fc31d9ca"><code>b61f588</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14622">#14622</a>
from chrisburr/fix-14608-initial-conftest-test-subdir</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/9a567e009f4d2da3ce1721c6db3109cb5744d40a"><code>9a567e0</code></a>
[automated] Update plugin list (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14617">#14617</a>)
(<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14618">#14618</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/ef8b2993e5b48639e4a3d97d0525df9760781384"><code>ef8b299</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14620">#14620</a>
from pytest-dev/patchback/backports/9.1.x/680f9f3ed...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/66abd0784d4cb7c1ba44ab9a8896506cd4985acc"><code>66abd07</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14220">#14220</a>
from bysiber/fix-stale-iexp-raisesgroup</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/79fbf93b666cac5f27c9dad047943d47b766c8d5"><code>79fbf93</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14612">#14612</a>
from pytest-dev/patchback/backports/9.1.x/974ed48b6...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/0d312eb876177e9f1c04262b54060a41034ebf5c"><code>0d312eb</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14611">#14611</a>
from bluetech/parametrize-argvalues-typing</li>
<li>Additional commits viewable in <a
href="https://github.com/pytest-dev/pytest/compare/9.1.0...9.1.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `ruff` from 0.15.18 to 0.15.20
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/releases">ruff's
releases</a>.</em></p>
<blockquote>
<h2>0.15.20</h2>
<h2>Release Notes</h2>
<p>Released on 2026-06-25.</p>
<h3>Preview features</h3>
<ul>
<li>Allow human-readable names in rule selectors (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25887">#25887</a>)</li>
<li>Emit a warning instead of an error for unknown rule selectors (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26113">#26113</a>)</li>
<li>Match <code>noqa</code> shebang handling in <code>ruff:ignore</code>
comments (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26286">#26286</a>)</li>
<li>[<code>ruff</code>] Remove <code>pytest-fixture-autouse</code>
(<code>RUF076</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26240">#26240</a>,
<a
href="https://redirect.github.com/astral-sh/ruff/pull/26371">#26371</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Add versioning sections to custom crate READMEs (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26317">#26317</a>)</li>
<li>Update <code>ruff_python_parser</code> README for crates.io (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26315">#26315</a>)</li>
<li>[<code>perflint</code>] Clarify that <code>PERF402</code> applies to
any iterable (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26242">#26242</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/dhruvmanila"><code>@​dhruvmanila</code></a></li>
<li><a
href="https://github.com/MichaReiser"><code>@​MichaReiser</code></a></li>
<li><a href="https://github.com/ntBre"><code>@​ntBre</code></a></li>
<li><a
href="https://github.com/trilamsr"><code>@​trilamsr</code></a></li>
</ul>
<h2>Install ruff 0.15.20</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-installer.sh
| sh
</code></pre>
<h3>Install prebuilt binaries via powershell script</h3>
<pre lang="sh"><code>powershell -ExecutionPolicy Bypass -c &quot;irm
https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-installer.ps1
| iex&quot;
</code></pre>
<h2>Download ruff 0.15.20</h2>
<table>
<thead>
<tr>
<th>File</th>
<th>Platform</th>
<th>Checksum</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-aarch64-apple-darwin.tar.gz">ruff-aarch64-apple-darwin.tar.gz</a></td>
<td>Apple Silicon macOS</td>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-aarch64-apple-darwin.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-x86_64-apple-darwin.tar.gz">ruff-x86_64-apple-darwin.tar.gz</a></td>
<td>Intel macOS</td>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-x86_64-apple-darwin.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-aarch64-pc-windows-msvc.zip">ruff-aarch64-pc-windows-msvc.zip</a></td>
<td>ARM64 Windows</td>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-aarch64-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-i686-pc-windows-msvc.zip">ruff-i686-pc-windows-msvc.zip</a></td>
<td>x86 Windows</td>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-i686-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-x86_64-pc-windows-msvc.zip">ruff-x86_64-pc-windows-msvc.zip</a></td>
<td>x64 Windows</td>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-x86_64-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-aarch64-unknown-linux-gnu.tar.gz">ruff-aarch64-unknown-linux-gnu.tar.gz</a></td>
<td>ARM64 Linux</td>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-aarch64-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-i686-unknown-linux-gnu.tar.gz">ruff-i686-unknown-linux-gnu.tar.gz</a></td>
<td>x86 Linux</td>
<td><a
href="https://releases.astral.sh/github/ruff/releases/download/0.15.20/ruff-i686-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
</tbody>
</table>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's
changelog</a>.</em></p>
<blockquote>
<h2>0.15.20</h2>
<p>Released on 2026-06-25.</p>
<h3>Preview features</h3>
<ul>
<li>Allow human-readable names in rule selectors (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25887">#25887</a>)</li>
<li>Emit a warning instead of an error for unknown rule selectors (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26113">#26113</a>)</li>
<li>Match <code>noqa</code> shebang handling in <code>ruff:ignore</code>
comments (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26286">#26286</a>)</li>
<li>[<code>ruff</code>] Remove <code>pytest-fixture-autouse</code>
(<code>RUF076</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26240">#26240</a>,
<a
href="https://redirect.github.com/astral-sh/ruff/pull/26371">#26371</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Add versioning sections to custom crate READMEs (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26317">#26317</a>)</li>
<li>Update <code>ruff_python_parser</code> README for crates.io (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26315">#26315</a>)</li>
<li>[<code>perflint</code>] Clarify that <code>PERF402</code> applies to
any iterable (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26242">#26242</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/dhruvmanila"><code>@​dhruvmanila</code></a></li>
<li><a
href="https://github.com/MichaReiser"><code>@​MichaReiser</code></a></li>
<li><a href="https://github.com/ntBre"><code>@​ntBre</code></a></li>
<li><a
href="https://github.com/trilamsr"><code>@​trilamsr</code></a></li>
</ul>
<h2>0.15.19</h2>
<p>Released on 2026-06-23.</p>
<h3>Preview features</h3>
<ul>
<li>Support human-readable names when hovering suppression comments and
in code actions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26114">#26114</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Fall back to default settings when editor-only settings are invalid
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/26244">#26244</a>)</li>
<li>Fix panic when inserting text at a notebook cell boundary (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26111">#26111</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>pylint</code>] Update fix suggestions for
<code>__floor__</code>, <code>__trunc__</code>,
<code>__length_hint__</code>, and <code>__matmul__</code> variants
(<code>PLC2801</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26239">#26239</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Avoid allocating when parsing single string literals (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26200">#26200</a>)</li>
<li>Avoid reallocating singleton call arguments (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26223">#26223</a>)</li>
<li>Lazily create source files for lint diagnostics (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26226">#26226</a>)</li>
<li>Optimize formatter text width and indentation (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26236">#26236</a>)</li>
<li>Reserve capacity for builtin bindings (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26229">#26229</a>)</li>
<li>Skip repeated-key checks for singleton dictionaries (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26228">#26228</a>)</li>
<li>Use ArrayVec for qualified name segments (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26224">#26224</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ruff/commit/f82a36b6baf8c0547a17bbde6c0d927ccd45d938"><code>f82a36b</code></a>
Bump 0.15.20 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26376">#26376</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/af329438b55d5aa8ca28725f17abbae63b5c815d"><code>af32943</code></a>
Improve the summarise-ecosystem-results skill (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26378">#26378</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/485ebab5de692aad34720898cd80245b6092ba86"><code>485ebab</code></a>
Remove <code>RUF076</code> name from schema (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26371">#26371</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/ef81835ce3da615346584dc446de0fe26bfc154b"><code>ef81835</code></a>
[ty] Implement rust-analyzer's &quot;Click for full compiler
diagnostic&quot; feature (...</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/572b31e2377a701a7365f069f6ef87002962cb2b"><code>572b31e</code></a>
[<code>ruff</code>] Remove <code>pytest-fixture-autouse</code>
(<code>RUF076</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26240">#26240</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/f703f219d7fddd1a4abba9fa81e34773c185a4b9"><code>f703f21</code></a>
Allow human-readable names in rule selectors (<a
href="https://redirect.github.com/astral-sh/ruff/issues/25887">#25887</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/0d726b28ee35413926145ffef4ac1143cae9ea1e"><code>0d726b2</code></a>
[ty] Reuse equality semantics for membership compatibility (<a
href="https://redirect.github.com/astral-sh/ruff/issues/25955">#25955</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/dbe6e9848c3f78fdaf543cfc000939ebc73b5f00"><code>dbe6e98</code></a>
[ty] Infer definite equality comparison results (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26337">#26337</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/e700ea357a2196d76b58eb89b2031ddb5ca896e0"><code>e700ea3</code></a>
[ty] Prove TypedDict structural patterns exhaustive (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26285">#26285</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/6a0d2ec93959d966430b8c39d4de5c3e8a88e911"><code>6a0d2ec</code></a>
[ty] Widen inferred class-valued instance attributes (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26338">#26338</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ruff/compare/0.15.18...0.15.20">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-29 12:23:19 -04:00
dependabot[bot] 995d8d6631 Bump @types/node from 25.9.3 to 26.0.0 in /src/frontend (#1092)
Bumps
[@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)
from 25.9.3 to 26.0.0.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@types/node&package-manager=npm_and_yarn&previous-version=25.9.3&new-version=26.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-26 14:54:15 -04:00
dependabot[bot] 9a27334d9c Bump the gh-actions group with 2 updates (#1093)
Bumps the gh-actions group with 2 updates:
[actions/checkout](https://github.com/actions/checkout) and
[dorny/paths-filter](https://github.com/dorny/paths-filter).

Updates `actions/checkout` from 4 to 7
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/releases">actions/checkout's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>block checking out fork pr for pull_request_target and workflow_run
by <a href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2454">actions/checkout#2454</a></li>
<li>Bump actions/publish-immutable-action from 0.0.3 to 0.0.4 in the
minor-actions-dependencies group across 1 directory by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2458">actions/checkout#2458</a></li>
<li>Bump flatted from 3.3.1 to 3.4.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2460">actions/checkout#2460</a></li>
<li>Bump js-yaml from 4.1.0 to 4.2.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2461">actions/checkout#2461</a></li>
<li>Bump <code>@​actions/core</code> and
<code>@​actions/tool-cache</code> and Remove uuid by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2459">actions/checkout#2459</a></li>
<li>upgrade module to esm and update dependencies by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2463">actions/checkout#2463</a></li>
<li>Bump the minor-npm-dependencies group across 1 directory with 3
updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2462">actions/checkout#2462</a></li>
<li>getting ready for checkout v7 release by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2464">actions/checkout#2464</a></li>
<li>update error wording by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2467">actions/checkout#2467</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/2454">actions/checkout#2454</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v6.0.3...v7.0.0">https://github.com/actions/checkout/compare/v6.0.3...v7.0.0</a></p>
<h2>v6.0.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Update changelog by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2357">actions/checkout#2357</a></li>
<li>fix: expand merge commit SHA regex and add SHA-256 test cases by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li>
<li>Fix checkout init for SHA-256 repositories by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2439">actions/checkout#2439</a></li>
<li>Update changelog for v6.0.3 by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2446">actions/checkout#2446</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/yaananth"><code>@​yaananth</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v6...v6.0.3">https://github.com/actions/checkout/compare/v6...v6.0.3</a></p>
<h2>v6.0.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Add orchestration_id to git user-agent when ACTIONS_ORCHESTRATION_ID
is set by <a
href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2355">actions/checkout#2355</a></li>
<li>Fix tag handling: preserve annotations and explicit fetch-tags by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2356">actions/checkout#2356</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v6.0.1...v6.0.2">https://github.com/actions/checkout/compare/v6.0.1...v6.0.2</a></p>
<h2>v6.0.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Update all references from v5 and v4 to v6 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2314">actions/checkout#2314</a></li>
<li>Add worktree support for persist-credentials includeIf by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2327">actions/checkout#2327</a></li>
<li>Clarify v6 README by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2328">actions/checkout#2328</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v6...v6.0.1">https://github.com/actions/checkout/compare/v6...v6.0.1</a></p>
<h2>v6.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update README to include Node.js 24 support details and requirements
by <a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2248">actions/checkout#2248</a></li>
<li>Persist creds to a separate file by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2286">actions/checkout#2286</a></li>
<li>v6-beta by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2298">actions/checkout#2298</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/checkout/commit/9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0"><code>9c091bb</code></a>
update error wording (<a
href="https://redirect.github.com/actions/checkout/issues/2467">#2467</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/1044a6dea927916f2c38ba5aeffbc0a847b1221a"><code>1044a6d</code></a>
getting ready for checkout v7 release (<a
href="https://redirect.github.com/actions/checkout/issues/2464">#2464</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/f0282184c7ce73ab54c7e4ab5a617122602e575f"><code>f028218</code></a>
Bump the minor-npm-dependencies group across 1 directory with 3 updates
(<a
href="https://redirect.github.com/actions/checkout/issues/2462">#2462</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/d914b262ffc244530a203ab40decab34c3abf34d"><code>d914b26</code></a>
upgrade module to esm and update dependencies (<a
href="https://redirect.github.com/actions/checkout/issues/2463">#2463</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/537c7ef99cef6e5ddb5e7ff5d16d14510503801d"><code>537c7ef</code></a>
Bump <code>@​actions/core</code> and <code>@​actions/tool-cache</code>
and Remove uuid (<a
href="https://redirect.github.com/actions/checkout/issues/2459">#2459</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/130a169078a413d3a5246a393625e8e742f387f6"><code>130a169</code></a>
Bump js-yaml from 4.1.0 to 4.2.0 (<a
href="https://redirect.github.com/actions/checkout/issues/2461">#2461</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/7d09575332117a40b46e5e020664df234cd416f3"><code>7d09575</code></a>
Bump flatted from 3.3.1 to 3.4.2 (<a
href="https://redirect.github.com/actions/checkout/issues/2460">#2460</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/0f9f3aa320cb53abeb534aeb54048075d9697a0e"><code>0f9f3aa</code></a>
Bump actions/publish-immutable-action (<a
href="https://redirect.github.com/actions/checkout/issues/2458">#2458</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/f9e715a95fcd1f9253f77dd28f11e88d2d6460c7"><code>f9e715a</code></a>
block checking out fork pr for pull_request_target and workflow_run (<a
href="https://redirect.github.com/actions/checkout/issues/2454">#2454</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/df4cb1c069e1874edd31b4311f1884172cec0e10"><code>df4cb1c</code></a>
Update changelog for v6.0.3 (<a
href="https://redirect.github.com/actions/checkout/issues/2446">#2446</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/checkout/compare/v4...v7">compare
view</a></li>
</ul>
</details>
<br />

Updates `dorny/paths-filter` from 3 to 4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/dorny/paths-filter/releases">dorny/paths-filter's
releases</a>.</em></p>
<blockquote>
<h2>v4.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>feat: update action runtime to node24 by <a
href="https://github.com/saschabratton"><code>@​saschabratton</code></a>
in <a
href="https://redirect.github.com/dorny/paths-filter/pull/294">dorny/paths-filter#294</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/saschabratton"><code>@​saschabratton</code></a>
made their first contribution in <a
href="https://redirect.github.com/dorny/paths-filter/pull/294">dorny/paths-filter#294</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/dorny/paths-filter/compare/v3.0.3...v4.0.0">https://github.com/dorny/paths-filter/compare/v3.0.3...v4.0.0</a></p>
<h2>v3.0.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Add missing predicate-quantifier by <a
href="https://github.com/wardpeet"><code>@​wardpeet</code></a> in <a
href="https://redirect.github.com/dorny/paths-filter/pull/279">dorny/paths-filter#279</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/wardpeet"><code>@​wardpeet</code></a>
made their first contribution in <a
href="https://redirect.github.com/dorny/paths-filter/pull/279">dorny/paths-filter#279</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/dorny/paths-filter/compare/v3...v3.0.3">https://github.com/dorny/paths-filter/compare/v3...v3.0.3</a></p>
<h2>v3.0.2</h2>
<h2>What's Changed</h2>
<ul>
<li>feat: add config parameter for predicate quantifier by <a
href="https://github.com/petermetz"><code>@​petermetz</code></a> in <a
href="https://redirect.github.com/dorny/paths-filter/pull/224">dorny/paths-filter#224</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/petermetz"><code>@​petermetz</code></a>
made their first contribution in <a
href="https://redirect.github.com/dorny/paths-filter/pull/224">dorny/paths-filter#224</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/dorny/paths-filter/compare/v3...v3.0.2">https://github.com/dorny/paths-filter/compare/v3...v3.0.2</a></p>
<h2>v3.0.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Compare base and ref when token is empty by <a
href="https://github.com/frouioui"><code>@​frouioui</code></a> in <a
href="https://redirect.github.com/dorny/paths-filter/pull/133">dorny/paths-filter#133</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/frouioui"><code>@​frouioui</code></a>
made their first contribution in <a
href="https://redirect.github.com/dorny/paths-filter/pull/133">dorny/paths-filter#133</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/dorny/paths-filter/compare/v3...v3.0.1">https://github.com/dorny/paths-filter/compare/v3...v3.0.1</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md">dorny/paths-filter's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>v4.0.0</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/294">Update
action runtime to node24</a></li>
</ul>
<h2>v3.0.3</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/279">Add
missing predicate-quantifier</a></li>
</ul>
<h2>v3.0.2</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/224">Add
config parameter for predicate quantifier</a></li>
</ul>
<h2>v3.0.1</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/133">Compare
base and ref when token is empty</a></li>
</ul>
<h2>v3.0.0</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/210">Update to
Node.js 20</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/215">Update
all dependencies</a></li>
</ul>
<h2>v2.11.1</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/167">Update
@​actions/core to v1.10.0 - Fixes warning about deprecated
set-output</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/168">Document
need for pull-requests: read permission</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/164">Updating
to actions/checkout@v3</a></li>
</ul>
<h2>v2.11.0</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/157">Set
list-files input parameter as not required</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/161">Update
Node.js</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/162">Fix
incorrect handling of Unicode characters in exec()</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/163">Use
Octokit pagination</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/160">Updates
real world links</a></li>
</ul>
<h2>v2.10.2</h2>
<ul>
<li><a href="https://redirect.github.com/dorny/paths-filter/pull/91">Fix
getLocalRef() returns wrong ref</a></li>
</ul>
<h2>v2.10.1</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/85">Improve
robustness of change detection</a></li>
</ul>
<h2>v2.10.0</h2>
<ul>
<li><a href="https://redirect.github.com/dorny/paths-filter/pull/82">Add
ref input parameter</a></li>
<li><a href="https://redirect.github.com/dorny/paths-filter/pull/83">Fix
change detection in PR when pullRequest.changed_files is
incorrect</a></li>
</ul>
<h2>v2.9.3</h2>
<ul>
<li><a href="https://redirect.github.com/dorny/paths-filter/pull/78">Fix
change detection when base is a tag</a></li>
</ul>
<h2>v2.9.2</h2>
<ul>
<li><a href="https://redirect.github.com/dorny/paths-filter/pull/75">Fix
fetching git history</a></li>
</ul>
<h2>v2.9.1</h2>
<ul>
<li><a href="https://redirect.github.com/dorny/paths-filter/pull/74">Fix
fetching git history + fallback to unshallow repo</a></li>
</ul>
<h2>v2.9.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/dorny/paths-filter/commit/fbd0ab8f3e69293af611ebaee6363fc25e6d187d"><code>fbd0ab8</code></a>
feat: add merge_group event support</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/efb1da7ce8d89bbc261191e5a2dc1453c3837339"><code>efb1da7</code></a>
feat: add dist/ freshness check to PR workflow</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/d8f7b061b24c30a325ff314b76c37adb05b041ce"><code>d8f7b06</code></a>
Merge pull request <a
href="https://redirect.github.com/dorny/paths-filter/issues/302">#302</a>
from dorny/issue-299</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/addbc147a95845176e1bc013a012fbf1d366389a"><code>addbc14</code></a>
Update README for v4</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/9d7afb8d214ad99e78fbd4247752c4caed2b6e4c"><code>9d7afb8</code></a>
Update CHANGELOG for v4.0.0</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/782470c5d953cae2693d643172b14e01bacb71f3"><code>782470c</code></a>
Merge branch 'releases/v3'</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/ce10459c8b92cd8901166c0a222fbb033ef39365"><code>ce10459</code></a>
Merge pull request <a
href="https://redirect.github.com/dorny/paths-filter/issues/294">#294</a>
from saschabratton/master</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/5f40380c5482e806c81cec080f5192e7234d8fe9"><code>5f40380</code></a>
feat: update action runtime to node24</li>
<li>See full diff in <a
href="https://github.com/dorny/paths-filter/compare/v3...v4">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-26 14:54:07 -04:00
dependabot[bot] 76c55a488c Bump node from 156b55f to a0b9bf0 in the docker-base-image-digests group (#1089)
Bumps the docker-base-image-digests group with 1 update: node.

Updates `node` from `156b55f` to `a0b9bf0`


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=node&package-manager=docker&previous-version=24-alpine&new-version=24-alpine)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-26 14:53:45 -04:00
dependabot[bot] 08d0a04cf2 Bump python from 44dd044 to 63a4c7f (#1090)
Bumps python from `44dd044` to `63a4c7f`.


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=python&package-manager=docker&previous-version=3.14.6-slim&new-version=3.14.6-slim)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-26 14:53:28 -04:00
CaliBrain 36f9a52987 Annotate both the per-platform manifests and the multi-arch image index (#1088)
Fixes #1087
2026-06-24 11:56:57 -04:00
CaliBrain 1536b91a46 Fix e2e scipts (#1086) 2026-06-24 00:09:06 -04:00
CaliBrain fea63269e3 the e2e platform test profiles live in an env/ dir and must be tracked (#1085) 2026-06-23 21:28:25 -04:00
CaliBrain 7886ba7038 fix astral-sh-setup-uv (#1084) 2026-06-23 17:43:59 -04:00
CaliBrain 1c19326bd0 e2e testing 2 (#1083) 2026-06-23 16:58:24 -04:00
CaliBrain 2f70ed36e4 e2e testing (#1082) 2026-06-23 15:38:46 -04:00
dependabot[bot] 8d54cff383 Bump the python-deps group with 3 updates (#1081)
Bumps the python-deps group with 3 updates:
[tqdm](https://github.com/tqdm/tqdm),
[seleniumbase](https://github.com/seleniumbase/SeleniumBase) and
[ruff](https://github.com/astral-sh/ruff).

Updates `tqdm` from 4.68.2 to 4.68.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tqdm/tqdm/releases">tqdm's
releases</a>.</em></p>
<blockquote>
<h2>tqdm v4.68.3 stable</h2>
<ul>
<li><code>utils</code>: delay <code>os.get_terminal_size</code> (<a
href="https://redirect.github.com/tqdm/tqdm/issues/1763">#1763</a> &lt;-
<a
href="https://redirect.github.com/tqdm/tqdm/issues/1760">#1760</a>)</li>
<li><code>autonotebook</code>: support QtConsole, Spyder, JupyterLite
(<a href="https://redirect.github.com/tqdm/tqdm/issues/1763">#1763</a>,
<a href="https://redirect.github.com/tqdm/tqdm/issues/1628">#1628</a>,
<a href="https://redirect.github.com/tqdm/tqdm/issues/1559">#1559</a>
&lt;- <a
href="https://redirect.github.com/tqdm/tqdm/issues/1283">#1283</a>, <a
href="https://redirect.github.com/tqdm/tqdm/issues/1098">#1098</a>, <a
href="https://redirect.github.com/tqdm/tqdm/issues/512">#512</a>)</li>
<li>minor docs updates
<ul>
<li>fix typo (<a
href="https://redirect.github.com/tqdm/tqdm/issues/1762">#1762</a>)</li>
<li>use <code>git-fame</code></li>
</ul>
</li>
<li>misc minor framework updates
<ul>
<li>fix &amp; update CI build</li>
<li>pre-commit: add docs &amp; metadata generation</li>
<li>move <code>tox.ini</code> -&gt; <code>pyproject.toml</code>, move
<code>tox-gh-actions</code> -&gt; <code>tox-gh</code></li>
<li>add Python 3.14, drop 3.7 support</li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/tqdm/tqdm/commit/9aff6090b6fa98c88a2dbff8bd9e2a68d9a99c1b"><code>9aff609</code></a>
bump version, merge pull request <a
href="https://redirect.github.com/tqdm/tqdm/issues/1763">#1763</a> from
tqdm/jupyterlite</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/9872f80e0d9ceb8377b45c714ec8f6ae66bf9ef9"><code>9872f80</code></a>
drop date from snap version str to fix pydantic</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/b82933405f6dc19dd8f667be57fd2058a00f75c7"><code>b829334</code></a>
support QtConsole, Spyder, JupyterLite</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/6fa4867b27dd4852204aa13f73300db41791cd46"><code>6fa4867</code></a>
delay os.get_terminal_size</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/748e107287c9a66e3a366cd5366bd9a8bcbe33ae"><code>748e107</code></a>
lint</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/e6e29eb7a191d366d21b93817c6de3857b664367"><code>e6e29eb</code></a>
move tox.ini -&gt; pyproject.toml</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/617e4faf67d6cc59d42e3b2665b0c1199a665db4"><code>617e4fa</code></a>
tox-gh-actions -&gt; tox-gh</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/c49c50d83124e514d2b6bb549661ff1e2fa44504"><code>c49c50d</code></a>
add python 3.14, drop python 3.7 support</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/fa2bcdb4ad68d4e95bd49460d8d590451cf478ba"><code>fa2bcdb</code></a>
pre-commit: tqdm.1</li>
<li><a
href="https://github.com/tqdm/tqdm/commit/1ec798811a960294d74d42d35b6614e3273e9fe3"><code>1ec7988</code></a>
auto git-fame</li>
<li>Additional commits viewable in <a
href="https://github.com/tqdm/tqdm/compare/v4.68.2...v4.68.3">compare
view</a></li>
</ul>
</details>
<br />

Updates `seleniumbase` from 4.49.14 to 4.50.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.50.1 - Fix Layout in Recorder App</h2>
<h2>Fix Layout in Recorder App</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/77cb1e267c04f60c8329ae6a00eb581d003d22fc">Fix
the layout of the SeleniumBase Recorder App</a></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>Fix Layout in Recorder App by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4405">seleniumbase/SeleniumBase#4405</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.50.0...v4.50.1">https://github.com/seleniumbase/SeleniumBase/compare/v4.50.0...v4.50.1</a></p>
<h2>4.50.0 - Improve Recorder and Fix Bugs</h2>
<h2>Improve Recorder and Fix Bugs</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/04acb5e8dc1c3621fe8e36529023a4fae8eebc7f">Improve
the desktop Recorder App and Recorder Mode</a>
-- This resolves <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4403">seleniumbase/SeleniumBase#4403</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/8a9a8eb8dbfa6f90166564769fea5244eafaa32f">Fix
sb.solve_captcha() on Windows for Slider CAPTCHAs</a>
-- This resolves <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4399">seleniumbase/SeleniumBase#4399</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/8a39a469f0b09b7aff6e7b3e2dc0a7608dbdb027">Prevent
cleanup warnings on Windows</a>
-- This resolves <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4400">seleniumbase/SeleniumBase#4400</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/d72f80972432117bb23e9a37f2f1e6c8cdbac365">Fix
issue with get_title() failing at random</a>
-- This resolves <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4401">seleniumbase/SeleniumBase#4401</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/13796feaaab122cfa8678644d95644d50cd5485f">Update
the docs</a>
-- It's mostly around the updated Recorder</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/221adbf8349cf0632290be5ec8fd1c135c4b9dac">Refresh
Python dependencies</a>
-- <code>selenium</code> and <code>certifi</code></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>Improved Recorder App and Bug Fixes by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4404">seleniumbase/SeleniumBase#4404</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.49.14...v4.50.0">https://github.com/seleniumbase/SeleniumBase/compare/v4.49.14...v4.50.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/93bf553426926a512457dc9e64a5a936e723041a"><code>93bf553</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4405">#4405</a>
from seleniumbase/fix-recorder-app-layout</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/fd8a6965f102f87f548ca2ca07a143ad90e9bd50"><code>fd8a696</code></a>
Version 4.50.1</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/18ee81c21f2a899ca17891c1cee6168ec146a54e"><code>18ee81c</code></a>
Update Recorder App image in a ReadMe</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/77cb1e267c04f60c8329ae6a00eb581d003d22fc"><code>77cb1e2</code></a>
Fix the layout of the SeleniumBase Recorder App</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/0c5867d71a4d8b966e61b18b33ac90c9b28ae130"><code>0c5867d</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4404">#4404</a>
from seleniumbase/improved-recorder-app-and-bug-fixes</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/a5ac4e8d5b8ae02a591d84c73494e4caaca2c83f"><code>a5ac4e8</code></a>
Version 4.50.0</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/221adbf8349cf0632290be5ec8fd1c135c4b9dac"><code>221adbf</code></a>
Refresh Python dependencies</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/27b2fb0a38cdfb5d90aea74235cae8e655c2df1a"><code>27b2fb0</code></a>
Update examples</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/13796feaaab122cfa8678644d95644d50cd5485f"><code>13796fe</code></a>
Update the docs</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/d72f80972432117bb23e9a37f2f1e6c8cdbac365"><code>d72f809</code></a>
Fix issue with <code>get_title()</code> failing at random</li>
<li>Additional commits viewable in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.49.14...v4.50.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `ruff` from 0.15.17 to 0.15.18
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/releases">ruff's
releases</a>.</em></p>
<blockquote>
<h2>0.15.18</h2>
<h2>Release Notes</h2>
<p>Released on 2026-06-18.</p>
<h3>Preview features</h3>
<ul>
<li>Handle nested <code>ruff:ignore</code> comments (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25791">#25791</a>)</li>
<li>Stop displaying severity in output (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26050">#26050</a>)</li>
<li>Use human-readable names in CLI output (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25937">#25937</a>)</li>
<li>Use human-readable names in LSP and playground diagnostics (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26058">#26058</a>)</li>
<li>[<code>pydocstyle</code>] Prevent property docstrings starting with
verbs (<code>D421</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23775">#23775</a>)</li>
<li>[<code>flake8-pyi</code>] Extend <code>PYI033</code> to Python files
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/26129">#26129</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Detect equivalent numeric mapping keys (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26009">#26009</a>)</li>
<li>Detect mapping keys equivalent to booleans (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25982">#25982</a>)</li>
<li>Detect repeated signed and complex dictionary keys (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26007">#26007</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>flake8-pyi</code>] Rename <code>PYI033</code> to
<code>legacy-type-comment</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26131">#26131</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Use <code>ThinVec</code> for call keywords (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25999">#25999</a>)</li>
<li>Inline parser recovery context checks (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26038">#26038</a>)</li>
<li>Match parser keywords as bytes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26037">#26037</a>)</li>
<li>Move value parsing out of lexing (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25360">#25360</a>)</li>
</ul>
<h3>Server</h3>
<ul>
<li>Render subdiagnostics and secondary annotations as related
information (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26011">#26011</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Update fix availability for always-fixable rules (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26091">#26091</a>)</li>
<li>[<code>flake8-tidy-imports</code>] Add fix safety section
(<code>TID252</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/17491">#17491</a>)</li>
</ul>
<h3>Parser</h3>
<ul>
<li>Reject <code>__debug__</code> lambda parameters (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26022">#26022</a>)</li>
<li>Reject <code>_</code> as a match-pattern target (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25977">#25977</a>)</li>
<li>Reject multiple starred names in sequence patterns (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25976">#25976</a>)</li>
<li>Reject parenthesized star imports (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26021">#26021</a>)</li>
<li>Reject starred comprehension targets (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26023">#26023</a>)</li>
<li>Reject unparenthesized generator expressions in class bases (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25978">#25978</a>)</li>
<li>Reject <code>yield</code> expressions after commas (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26024">#26024</a>)</li>
<li>Validate function type parameter default order (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25981">#25981</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's
changelog</a>.</em></p>
<blockquote>
<h2>0.15.18</h2>
<p>Released on 2026-06-18.</p>
<h3>Preview features</h3>
<ul>
<li>Handle nested <code>ruff:ignore</code> comments (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25791">#25791</a>)</li>
<li>Stop displaying severity in output (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26050">#26050</a>)</li>
<li>Use human-readable names in CLI output (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25937">#25937</a>)</li>
<li>Use human-readable names in LSP and playground diagnostics (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26058">#26058</a>)</li>
<li>[<code>pydocstyle</code>] Prevent property docstrings starting with
verbs (<code>D421</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23775">#23775</a>)</li>
<li>[<code>flake8-pyi</code>] Extend <code>PYI033</code> to Python files
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/26129">#26129</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Detect equivalent numeric mapping keys (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26009">#26009</a>)</li>
<li>Detect mapping keys equivalent to booleans (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25982">#25982</a>)</li>
<li>Detect repeated signed and complex dictionary keys (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26007">#26007</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>flake8-pyi</code>] Rename <code>PYI033</code> to
<code>legacy-type-comment</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26131">#26131</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Use <code>ThinVec</code> for call keywords (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25999">#25999</a>)</li>
<li>Inline parser recovery context checks (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26038">#26038</a>)</li>
<li>Match parser keywords as bytes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26037">#26037</a>)</li>
<li>Move value parsing out of lexing (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25360">#25360</a>)</li>
</ul>
<h3>Server</h3>
<ul>
<li>Render subdiagnostics and secondary annotations as related
information (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26011">#26011</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Update fix availability for always-fixable rules (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26091">#26091</a>)</li>
<li>[<code>flake8-tidy-imports</code>] Add fix safety section
(<code>TID252</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/17491">#17491</a>)</li>
</ul>
<h3>Parser</h3>
<ul>
<li>Reject <code>__debug__</code> lambda parameters (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26022">#26022</a>)</li>
<li>Reject <code>_</code> as a match-pattern target (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25977">#25977</a>)</li>
<li>Reject multiple starred names in sequence patterns (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25976">#25976</a>)</li>
<li>Reject parenthesized star imports (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26021">#26021</a>)</li>
<li>Reject starred comprehension targets (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26023">#26023</a>)</li>
<li>Reject unparenthesized generator expressions in class bases (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25978">#25978</a>)</li>
<li>Reject <code>yield</code> expressions after commas (<a
href="https://redirect.github.com/astral-sh/ruff/pull/26024">#26024</a>)</li>
<li>Validate function type parameter default order (<a
href="https://redirect.github.com/astral-sh/ruff/pull/25981">#25981</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ruff/commit/6686f63404207bfdffe8ab0acb25da72c3432190"><code>6686f63</code></a>
Bump 0.15.18 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26135">#26135</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/efbb7325ff5bb92b0a0c0a0efa0ac94e439ee367"><code>efbb732</code></a>
[ty] Suggest keyword-only arguments between variadic parameters (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26134">#26134</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/c256d5f573e66612556061bfd0af527136a23c30"><code>c256d5f</code></a>
[ty] Support <code>Annotated[Any, ...]</code> as a class base (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26133">#26133</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/19a4bea1af5b252798d85696f2f397dae91d303d"><code>19a4bea</code></a>
[flake8-pyi] Rename PYI033 to legacy-type-comment (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26131">#26131</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/1d9866cbd673dc68153ad162465e979c47e62a96"><code>1d9866c</code></a>
Bump ecosystem-analyzer commit (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26130">#26130</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/8656c731dded1e91c0e2713405e43bb2c4eb53bf"><code>8656c73</code></a>
[ty] Compact indexed AST node storage (<a
href="https://redirect.github.com/astral-sh/ruff/issues/25998">#25998</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/c17c8d9a060f839c0f9b69c69b85d3802ec5dfdf"><code>c17c8d9</code></a>
[ty] Garbage-collect cached constraint sets (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26116">#26116</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/ef0fb8fa264035974e57fda59e2baf196ec1fb9e"><code>ef0fb8f</code></a>
[ty] Fix bound TypeVar default cycle recovery (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26124">#26124</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/b83c02489df8bccda7313a2cdd8369c9b5d1b002"><code>b83c024</code></a>
[flake8-pyi] Extend PYI033 to Python files in preview (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26129">#26129</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/e8a5e38858558a68e94397152fd735cacb6ea3b4"><code>e8a5e38</code></a>
Update Rust crate zip to v8 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/26078">#26078</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ruff/compare/0.15.17...0.15.18">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 13:36:49 -04:00
dependabot[bot]andCaliBrain 63fa6213da Bump the npm-deps group in /src/frontend with 6 updates (#1077)
Bumps the npm-deps group in /src/frontend with 6 updates:

| Package | From | To |
| --- | --- | --- |
|
[@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite)
| `4.3.0` | `4.3.1` |
| [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) |
`6.16.1` | `6.17.0` |
| [oxfmt](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt) |
`0.54.0` | `0.55.0` |
| [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) |
`1.69.0` | `1.70.0` |
|
[tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss)
| `4.3.0` | `4.3.1` |
|
[vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)
| `4.1.8` | `4.1.9` |

Updates `@tailwindcss/vite` from 4.3.0 to 4.3.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tailwindlabs/tailwindcss/releases">@​tailwindcss/vite's
releases</a>.</em></p>
<blockquote>
<h2>v4.3.1</h2>
<h3>Added</h3>
<ul>
<li>Add <code>--silent</code> option to suppress output in
<code>@tailwindcss/cli</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20100">#20100</a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Remove deprecation warnings by using
<code>Module#registerHooks</code> instead of
<code>Module#register</code> on Node 26+ (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20028">#20028</a>)</li>
<li>Canonicalization: don't crash when plugin utilities throw for
unsupported values (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20052">#20052</a>)</li>
<li>Allow <code>@apply</code> to be used with CSS mixins (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/19427">#19427</a>)</li>
<li>Ensure <code>not-*</code> correctly negates <code>@container</code>
queries, including <code>style(…)</code> queries (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20059">#20059</a>)</li>
<li>Ensure <code>drop-shadow-*</code> color utilities work with custom
shadow values containing <code>calc(…)</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20080">#20080</a>)</li>
<li>Fix 'Sourcemap is likely to be incorrect' warnings when using
<code>@tailwindcss/vite</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20103">#20103</a>)</li>
<li>Ensure <code>@tailwindcss/webpack</code> can be installed in Rspack
projects without requiring <code>webpack</code> as a peer dependency (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20027">#20027</a>)</li>
<li>Canonicalization: don't suggest invalid <code>calc(…)</code>
expressions (e.g. <code>px-[calc(1rem+0px)]</code> →
<code>px-[calc(1rem+0)]</code>) (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20127">#20127</a>)</li>
<li>Canonicalization: avoid suggesting large spacing-scale values for
arbitrary lengths (e.g. <code>left-[99999px]</code> →
<code>left-[99999px]</code>, not <code>left-24999.75</code>) (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20130">#20130</a>)</li>
<li>Ensure <code>@tailwindcss/cli</code> in <code>--watch</code> mode
recovers when a tracked dependency is deleted and restored (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20137">#20137</a>)</li>
<li>Ensure standalone <code>@tailwindcss/cli</code> binaries are ignored
when scanning for class candidates (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20139">#20139</a>)</li>
<li>Ensure class candidates are extracted from Twig
<code>addClass(…)</code> and <code>removeClass(…)</code> calls (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20198">#20198</a>)</li>
<li>Don't crash in the Ruby or Vue preprocessors when scanning files
containing invalid UTF-8 bytes (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/19588">#19588</a>)</li>
<li>Allow <code>@variant</code> to be used inside <code>addBase</code>
(<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/19480">#19480</a>)</li>
<li>Ensure <code>@source</code> globs with symlinks are preserved (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20203">#20203</a>)</li>
<li>Ensure later <code>@source</code> rules can re-include files
excluded by earlier <code>@source not</code> rules (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20203">#20203</a>)</li>
<li>Upgrade: don't migrate empty class rules to invalid
<code>@utility</code> rules (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20205">#20205</a>)</li>
<li>Ensure transitions between <code>inset-shadow-none</code> and other
inset shadows work correctly (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20208">#20208</a>)</li>
<li>Ensure explicitly referenced <code>@source</code> directories are
scanned even when ignored by git (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20214">#20214</a>)</li>
<li>Ensure <code>@source</code> globs ending in <code>**/*</code>
preserve dynamic path segments to avoid scanning too many files (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20217">#20217</a>)</li>
<li>Canonicalization: don't fold <code>calc(…)</code> divisions when the
result would require high precision (e.g.
<code>w-[calc(100%/3.5)]</code> → <code>w-[calc(100%/3.5)]</code>, not
<code>w-[28.571428571428573%]</code>) (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20221">#20221</a>)</li>
<li>Serve ESM type declarations to ESM importers of
<code>@tailwindcss/postcss</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20228">#20228</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>Generate <code>0</code> instead of <code>calc(var(--spacing) *
0)</code> for spacing utilities like <code>m-0</code> and
<code>left-0</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20196">#20196</a>)</li>
<li>Generate <code>var(--spacing)</code> instead of
<code>calc(var(--spacing) * 1)</code> for spacing utilities like
<code>m-1</code> and <code>left-1</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20196">#20196</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md">@​tailwindcss/vite's
changelog</a>.</em></p>
<blockquote>
<h2>[4.3.1] - 2026-06-12</h2>
<h3>Added</h3>
<ul>
<li>Add <code>--silent</code> option to suppress output in
<code>@tailwindcss/cli</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20100">#20100</a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Remove deprecation warnings by using
<code>Module#registerHooks</code> instead of
<code>Module#register</code> on Node 26+ (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20028">#20028</a>)</li>
<li>Canonicalization: don't crash when plugin utilities throw for
unsupported values (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20052">#20052</a>)</li>
<li>Allow <code>@apply</code> to be used with CSS mixins (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/19427">#19427</a>)</li>
<li>Ensure <code>not-*</code> correctly negates <code>@container</code>
queries, including <code>style(…)</code> queries (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20059">#20059</a>)</li>
<li>Ensure <code>drop-shadow-*</code> color utilities work with custom
shadow values containing <code>calc(…)</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20080">#20080</a>)</li>
<li>Fix 'Sourcemap is likely to be incorrect' warnings when using
<code>@tailwindcss/vite</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20103">#20103</a>)</li>
<li>Ensure <code>@tailwindcss/webpack</code> can be installed in Rspack
projects without requiring <code>webpack</code> as a peer dependency (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20027">#20027</a>)</li>
<li>Canonicalization: don't suggest invalid <code>calc(…)</code>
expressions (e.g. <code>px-[calc(1rem+0px)]</code> →
<code>px-[calc(1rem+0)]</code>) (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20127">#20127</a>)</li>
<li>Canonicalization: avoid suggesting large spacing-scale values for
arbitrary lengths (e.g. <code>left-[99999px]</code> →
<code>left-[99999px]</code>, not <code>left-24999.75</code>) (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20130">#20130</a>)</li>
<li>Ensure <code>@tailwindcss/cli</code> in <code>--watch</code> mode
recovers when a tracked dependency is deleted and restored (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20137">#20137</a>)</li>
<li>Ensure standalone <code>@tailwindcss/cli</code> binaries are ignored
when scanning for class candidates (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20139">#20139</a>)</li>
<li>Ensure class candidates are extracted from Twig
<code>addClass(…)</code> and <code>removeClass(…)</code> calls (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20198">#20198</a>)</li>
<li>Don't crash in the Ruby or Vue preprocessors when scanning files
containing invalid UTF-8 bytes (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/19588">#19588</a>)</li>
<li>Allow <code>@variant</code> to be used inside <code>addBase</code>
(<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/19480">#19480</a>)</li>
<li>Ensure <code>@source</code> globs with symlinks are preserved (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20203">#20203</a>)</li>
<li>Ensure later <code>@source</code> rules can re-include files
excluded by earlier <code>@source not</code> rules (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20203">#20203</a>)</li>
<li>Upgrade: don't migrate empty class rules to invalid
<code>@utility</code> rules (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20205">#20205</a>)</li>
<li>Ensure transitions between <code>inset-shadow-none</code> and other
inset shadows work correctly (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20208">#20208</a>)</li>
<li>Ensure explicitly referenced <code>@source</code> directories are
scanned even when ignored by git (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20214">#20214</a>)</li>
<li>Ensure <code>@source</code> globs ending in <code>**/*</code>
preserve dynamic path segments to avoid scanning too many files (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20217">#20217</a>)</li>
<li>Canonicalization: don't fold <code>calc(…)</code> divisions when the
result would require high precision (e.g.
<code>w-[calc(100%/3.5)]</code> → <code>w-[calc(100%/3.5)]</code>, not
<code>w-[28.571428571428573%]</code>) (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20221">#20221</a>)</li>
<li>Serve ESM type declarations to ESM importers of
<code>@tailwindcss/postcss</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20228">#20228</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>Generate <code>0</code> instead of <code>calc(var(--spacing) *
0)</code> for spacing utilities like <code>m-0</code> and
<code>left-0</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20196">#20196</a>)</li>
<li>Generate <code>var(--spacing)</code> instead of
<code>calc(var(--spacing) * 1)</code> for spacing utilities like
<code>m-1</code> and <code>left-1</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20196">#20196</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/8a14a710102cae195f6811e8578bef9477bc6be9"><code>8a14a71</code></a>
4.3.1 (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite/issues/20226">#20226</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/73983e1cf5bc0ae721f4568cc24a5b5067b6b90b"><code>73983e1</code></a>
Fix 'Sourcemap is likely to be incorrect' warnings when using
`@tailwindcss/v...</li>
<li>See full diff in <a
href="https://github.com/tailwindlabs/tailwindcss/commits/v4.3.1/packages/@tailwindcss-vite">compare
view</a></li>
</ul>
</details>
<br />

Updates `knip` from 6.16.1 to 6.17.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/webpro-nl/knip/releases">knip's
releases</a>.</em></p>
<blockquote>
<h2>Release 6.17.0</h2>
<ul>
<li>chore(mcp): add package metadata links (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1783">#1783</a>)
(e3d93b930) - thanks <a
href="https://github.com/sh962214-hub"><code>@​sh962214-hub</code></a>!</li>
<li>fix(capacitor): detect iOS platform with Swift Package Manager (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1787">#1787</a>)
(e6cc533a8) - thanks <a
href="https://github.com/jthrilly"><code>@​jthrilly</code></a>!</li>
<li>Support ignoreIssues per workspace (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1782">#1782</a>)
(15a329a05)</li>
<li>Add commitlint.config.mts (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1784">#1784</a>)
(fa8eb6da8)</li>
<li>Treat scoped and tilde SCSS imports as packages (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1786">#1786</a>)
(98aa9623e)</li>
<li>Add followCursor setting for Imports/Exports views (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1788">#1788</a>)
(67a0be89f)</li>
<li>Flag undeclared sibling imports in published workspaces (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1792">#1792</a>)
(aeabff789)</li>
<li>Fix type-check errors in vscode-knip (12f266e94)</li>
<li>Note Markdown formatting with pnpm remark (bdffeec0d)</li>
<li>Bump some doc-related dependencies (333419303)</li>
<li>Update vscode-knip tool descriptions (be341788c)</li>
<li>Overhaul docs (55e3f3bda)</li>
<li>Improve mcp guidance (67483f0ac)</li>
<li>Fix repeated --fix-type arg (9bb0512ca)</li>
<li>Resolve module paths from selected tsconfig (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1794">#1794</a>)
(1c2f39833)</li>
<li>Update sanity snapshot (4ebce9c50)</li>
<li>Add tar to globally ignored binaries (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1796">#1796</a>)
(8c028e5fb)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/webpro-nl/knip/commit/dd0c682f9c41cfcd428458ece2cd32553824a550"><code>dd0c682</code></a>
Release knip@6.17.0</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/8c028e5fb25e989591689805c335ffd80a651bfd"><code>8c028e5</code></a>
Add tar to globally ignored binaries (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1796">#1796</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/1c2f398331fddc9beddfc438ac5ce529ec9e070c"><code>1c2f398</code></a>
Resolve module paths from selected tsconfig (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1794">#1794</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/9bb0512ca0e06f61117076fb8bfa806dfe3f7fb1"><code>9bb0512</code></a>
Fix repeated --fix-type arg</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/67483f0ac6ea6845631ffdef0425de6b41092e26"><code>67483f0</code></a>
Improve mcp guidance</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/aeabff789071e7451ed88744ac70621d6d29249f"><code>aeabff7</code></a>
Flag undeclared sibling imports in published workspaces (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1792">#1792</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/98aa9623e3794b6fe783e0ce2c11a0e4acf2b8c2"><code>98aa962</code></a>
Treat scoped and tilde SCSS imports as packages (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1786">#1786</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/fa8eb6da81066f835fbca05d640348bd9b2b8eda"><code>fa8eb6d</code></a>
Add commitlint.config.mts (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1784">#1784</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/15a329a0555b3496fe4018a39cb5cb734fbb9eba"><code>15a329a</code></a>
Support ignoreIssues per workspace (resolve <a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1782">#1782</a>)</li>
<li><a
href="https://github.com/webpro-nl/knip/commit/e6cc533a8772cdc4aba93cac376d214fb2fb5a58"><code>e6cc533</code></a>
fix(capacitor): detect iOS platform with Swift Package Manager (<a
href="https://github.com/webpro-nl/knip/tree/HEAD/packages/knip/issues/1787">#1787</a>)</li>
<li>See full diff in <a
href="https://github.com/webpro-nl/knip/commits/knip@6.17.0/packages/knip">compare
view</a></li>
</ul>
</details>
<br />

Updates `oxfmt` from 0.54.0 to 0.55.0
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/blob/main/npm/oxfmt/CHANGELOG.md">oxfmt's
changelog</a>.</em></p>
<blockquote>
<h2>[0.55.0] - 2026-06-15</h2>
<h3>🚀 Features</h3>
<ul>
<li>9a2788b linter/unicorn: Implement <code>prefer-export-from</code>
rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/22935">#22935</a>)
(AliceLanniste)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/commit/aa79b5be9915068f60bc361febf2e7496b92fc24"><code>aa79b5b</code></a>
release(apps): oxlint v1.70.0 &amp;&amp; oxfmt v0.55.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/23442">#23442</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/9a2788b3ae60c033a969fbba63733748f8c6130a"><code>9a2788b</code></a>
feat(linter/unicorn): implement <code>prefer-export-from</code> rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt/issues/22935">#22935</a>)</li>
<li>See full diff in <a
href="https://github.com/oxc-project/oxc/commits/oxfmt_v0.55.0/npm/oxfmt">compare
view</a></li>
</ul>
</details>
<br />

Updates `oxlint` from 1.69.0 to 1.70.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/releases">oxlint's
releases</a>.</em></p>
<blockquote>
<h2>oxlint v1.27.0 &amp;&amp; oxfmt v0.12.0</h2>
<h1>Oxlint v1.27.0</h1>
<h3>🚀 Features</h3>
<ul>
<li>222a8f0 linter/plugins: Implement
<code>SourceCode#isSpaceBetween</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15498">#15498</a>)
(overlookmotel)</li>
<li>2f9735d linter/plugins: Implement
<code>context.languageOptions</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15486">#15486</a>)
(overlookmotel)</li>
<li>bc731ff linter/plugins: Stub out all <code>Context</code> APIs (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15479">#15479</a>)
(overlookmotel)</li>
<li>5822cb4 linter/plugins: Add <code>extend</code> method to
<code>FILE_CONTEXT</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15477">#15477</a>)
(overlookmotel)</li>
<li>7b1e6f3 apps: Add pure rust binaries and release to github (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15469">#15469</a>)
(Boshen)</li>
<li>2a89b43 linter: Introduce debug assertions after fixes to assert
validity (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15389">#15389</a>)
(camc314)</li>
<li>ad3c45a editor: Add <code>oxc.path.node</code> option (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15040">#15040</a>)
(Sysix)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>6f3cd77 linter/no-var: Incorrect warning for blocks (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15504">#15504</a>)
(Hamir Mahal)</li>
<li>6957fb9 linter/plugins: Do not allow access to
<code>Context#id</code> in <code>createOnce</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15489">#15489</a>)
(overlookmotel)</li>
<li>7409630 linter/plugins: Allow access to <code>cwd</code> in
<code>createOnce</code> in ESLint interop mode (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15488">#15488</a>)
(overlookmotel)</li>
<li>732205e parser: Reject <code>using</code> / <code>await using</code>
in a switch <code>case</code> / <code>default</code> clause (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15225">#15225</a>)
(sapphi-red)</li>
<li>a17ca32 linter/plugins: Replace <code>Context</code> class (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15448">#15448</a>)
(overlookmotel)</li>
<li>ecf2f7b language_server: Fail gracefully when tsgolint executable
not found (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15436">#15436</a>)
(camc314)</li>
<li>3c8d3a7 lang-server: Improve logging in failure case for tsgolint
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15299">#15299</a>)
(camc314)</li>
<li>ef71410 linter: Use jsx if source type is JS in fix debug assertion
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15434">#15434</a>)
(camc314)</li>
<li>e32bbf6 linter/no-var: Handle TypeScript declare keyword in fixer
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15426">#15426</a>)
(camc314)</li>
<li>6565dbe linter/switch-case-braces: Skip comments when searching for
<code>:</code> token (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15425">#15425</a>)
(camc314)</li>
<li>85bd19a linter/prefer-class-fields: Insert value after type
annotation in fixer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15423">#15423</a>)
(camc314)</li>
<li>fde753e linter/plugins: Block access to
<code>context.settings</code> in <code>createOnce</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15394">#15394</a>)
(overlookmotel)</li>
<li>ddd9f9f linter/forward-ref-uses-ref: Dont suggest removing wrapper
in invalid positions (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15388">#15388</a>)
(camc314)</li>
<li>dac2a9c linter/no-template-curly-in-string: Remove fixer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15387">#15387</a>)
(camc314)</li>
<li>989b8e3 linter/no-var: Only fix to <code>const</code> if the var has
an initializer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15385">#15385</a>)
(camc314)</li>
<li>cc403f5 linter/plugins: Return empty object for unimplemented
parserServices (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15364">#15364</a>)
(magic-akari)</li>
</ul>
<h3>⚡ Performance</h3>
<ul>
<li>25d577e language_server: Start tools in parallel (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15500">#15500</a>)
(Sysix)</li>
<li>3c57291 linter/plugins: Optimize loops (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15449">#15449</a>)
(overlookmotel)</li>
<li>3166233 linter/plugins: Remove <code>Arc</code>s (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15431">#15431</a>)
(overlookmotel)</li>
<li>9de1322 linter/plugins: Lazily deserialize settings JSON (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15395">#15395</a>)
(overlookmotel)</li>
<li>3049ec2 linter/plugins: Optimize <code>deepFreezeSettings</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15392">#15392</a>)
(overlookmotel)</li>
<li>444ebfd linter/plugins: Use single object for
<code>parserServices</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15378">#15378</a>)
(overlookmotel)</li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>97d2104 linter: Update comment in lint.rs about default value for
tsconfig path (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15530">#15530</a>)
(Connor Shea)</li>
<li>2c6bd9e linter: Always refer as &quot;ES2015&quot; instead of
&quot;ES6&quot; (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15411">#15411</a>)
(sapphi-red)</li>
<li>a0c5203 linter/import/named: Update &quot;ES7&quot; comment in
examples (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15410">#15410</a>)
(sapphi-red)</li>
<li>3dc24b5 linter,minifier: Always refer as &quot;ES Modules&quot;
instead of &quot;ES6 Modules&quot; (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15409">#15409</a>)
(sapphi-red)</li>
<li>2ad77fb linter/no-this-before-super: Correct &quot;Why is this
bad?&quot; section (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15408">#15408</a>)
(sapphi-red)</li>
<li>57f0ce1 linter: Add backquotes where appropriate (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15407">#15407</a>)
(sapphi-red)</li>
</ul>
<h1>Oxfmt v0.12.0</h1>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md">oxlint's
changelog</a>.</em></p>
<blockquote>
<h2>[1.70.0] - 2026-06-15</h2>
<h3>🚀 Features</h3>
<ul>
<li>2e8bda4 linter/vue: Implement no-dupe-keys rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23350">#23350</a>)
(bab)</li>
<li>1490a0a linter/react: Implement react-compiler rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23202">#23202</a>)
(Boshen)</li>
<li>dd560ae linter/unicorn: Implement
<code>no-array-fill-with-reference-type</code> rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23397">#23397</a>)
(Mikhail Baev)</li>
<li>af36c2f linter: Add schema for
<code>react/jsx-curly-brace-presence</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23400">#23400</a>)
(WaterWhisperer)</li>
<li>47d34a3 linter: Add schema for <code>react/jsx-handler-names</code>
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23393">#23393</a>)
(WaterWhisperer)</li>
<li>f4250d0 linter: Add schema for <code>unicorn/import-style</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23386">#23386</a>)
(WaterWhisperer)</li>
<li>30c74ce linter: Add schema for
<code>jsx_a11y/no-noninteractive-element-to-interactive-role</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23384">#23384</a>)
(Sysix)</li>
<li>cfbe8dc linter: Add schema for
<code>jsx_a11y/no-interactive-element-to-noninteractive-role</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23382">#23382</a>)
(WaterWhisperer)</li>
<li>d15b7ff linter: Add schema for
<code>typescript/no-restricted-types</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23381">#23381</a>)
(WaterWhisperer)</li>
<li>028a811 linter: Add schema for
<code>jsx-a11y/media-has-caption</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23377">#23377</a>)
(Sysix)</li>
<li>b3b1038 linter: Add schema for
<code>jsx-a11y/label-has-associated-control</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23376">#23376</a>)
(Sysix)</li>
<li>7ada6b2 linter: Add schema for
<code>jsx_a11y/no-distracting-elements</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23379">#23379</a>)
(WaterWhisperer)</li>
<li>ee3dd49 linter: Add schema for
<code>jsx-a11y/img-redundant-alt</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23374">#23374</a>)
(Sysix)</li>
<li>df5f8dd linter: Add short descriptions to most lint rules. (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23365">#23365</a>)
(Connor Shea)</li>
<li>e3fd735 linter: Add schema for <code>jsx_a11y/alt-text</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23369">#23369</a>)
(Sysix)</li>
<li>0f2fff4 linter: Add schema for <code>react/exhaustive-deps</code>
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23372">#23372</a>)
(Mikhail Baev)</li>
<li>e3e4e10 linter: Add schema for
<code>react_perf/jsx-no-new-object-as-prop</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23368">#23368</a>)
(Mikhail Baev)</li>
<li>9366d44 linter: Add schema for <code>unicorn/prefer-at</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23366">#23366</a>)
(WaterWhisperer)</li>
<li>f57b55d linter: Add schema for <code>typescript/array-type</code>
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23355">#23355</a>)
(Sysix)</li>
<li>0dcf912 linter: Add schema for
<code>typescript/ban-ts-comment</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23354">#23354</a>)
(Sysix)</li>
<li>51fa83e linter: Add schema for
<code>react/no-did-update-set-state</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23357">#23357</a>)
(Mikhail Baev)</li>
<li>59db0bd linter: Add schema for
<code>consistent-generic-constructors</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23353">#23353</a>)
(Sysix)</li>
<li>c4775c0 linter: Add schema for
<code>typescript/consistent-type-assertions</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23349">#23349</a>)
(Sysix)</li>
<li>6e516f7 linter: Add schema for
<code>typescript/consistent-type-imports</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23348">#23348</a>)
(Sysix)</li>
<li>012134d linter: Add schema for
<code>react/jsx-no-target-blank</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23345">#23345</a>)
(WaterWhisperer)</li>
<li>0806aae linter: Add schema for
<code>jsx_a11y/no-noninteractive-tabindex</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23337">#23337</a>)
(Mikhail Baev)</li>
<li>0708b5a linter: Add schema for
<code>react/jsx-filename-extension</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23315">#23315</a>)
(Mikhail Baev)</li>
<li>150bce1 linter: Add schema for
<code>typescript/no-empty-object-type</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23309">#23309</a>)
(Sysix)</li>
<li>f9e36f1 linter: Add schema for
<code>typescript/no-duplicate-type-constituents</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23308">#23308</a>)
(Sysix)</li>
<li>937accf linter: Add schema for
<code>typescript/no-invalid-void-type</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23307">#23307</a>)
(Sysix)</li>
<li>3e042b9 linter: Add schema for
<code>typescript/no-misused-promises</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23306">#23306</a>)
(Sysix)</li>
<li>da212d1 linter: Add schema for
<code>typescript/no-unnecessary-condition</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23305">#23305</a>)
(Sysix)</li>
<li>f8f0d38 linter: Add schema for
<code>typescript/parameter-properties</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23304">#23304</a>)
(Sysix)</li>
<li>2275fc7 linter: Add schema for
<code>typescript/prefer-nullish-coalescing</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23302">#23302</a>)
(Sysix)</li>
<li>d353858 linter: Add schema for
<code>typescript/prefer-string-starts-ends-with</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23301">#23301</a>)
(Sysix)</li>
<li>03060f5 linter: Add schema for
<code>typescript/triple-slash-reference</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23300">#23300</a>)
(Sysix)</li>
<li>6619cee linter: Add schema for <code>promise/param-names</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23298">#23298</a>)
(Sysix)</li>
<li>8bf108e linter: Add schema for <code>promise/catch-or-return</code>
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23297">#23297</a>)
(Sysix)</li>
<li>48158d0 linter: Add schema for
<code>vitest/consistent-each-for</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23294">#23294</a>)
(Sysix)</li>
<li>7e74c98 linter: Add schema for
<code>vitest/consistent-test-filename</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23293">#23293</a>)
(Sysix)</li>
<li>ff94d4a linter: Add schema for
<code>vitest/consistent-vitest-vi</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23292">#23292</a>)
(Sysix)</li>
<li>2409a10 linter: Add schema for
<code>vitest/prefer-import-in-mock</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23291">#23291</a>)
(Sysix)</li>
<li>3d782b7 linter: Add schema for
<code>react/no-unstable-nested-components</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23287">#23287</a>)
(Mikhail Baev)</li>
<li>0a0bc2f linter/jsx-a11y: Add <code>allowedRedundantRoles</code>
option to <code>no-redundant-roles</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/22820">#22820</a>)
(bab)</li>
<li>80758a5 linter/vue: Implement no-side-effects-in-computed-properties
rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23282">#23282</a>)
(bab)</li>
<li>e3869ac linter: Add schema for
<code>react/no-object-type-as-default-prop</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23279">#23279</a>)
(Mikhail Baev)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/commit/aa79b5be9915068f60bc361febf2e7496b92fc24"><code>aa79b5b</code></a>
release(apps): oxlint v1.70.0 &amp;&amp; oxfmt v0.55.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23442">#23442</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/5cb8d1bcf0c63b34ba91e9f9c36e7a84c76cb496"><code>5cb8d1b</code></a>
fix(linter/jsx-a11y/control-has-associated-label): reject excessive
label dep...</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/f682e253892e8e77d7700d1053234e7a53c0dbca"><code>f682e25</code></a>
docs(linter): remove manually written options doc for
`eslint/prefer-arrow-ca...</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/2e8bda49991cfef9cb9182d37e9f304372f53839"><code>2e8bda4</code></a>
feat(linter/vue): implement no-dupe-keys rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23350">#23350</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/64c942cffe0e8829293d1d9f2f046b496759609a"><code>64c942c</code></a>
docs(linter): remove manually written options doc for
<code>eslint/no-sequences</code> (...</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/1490a0a0691908df0ff6659750ca2f3a29396c1a"><code>1490a0a</code></a>
feat(linter/react): implement react-compiler rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23202">#23202</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/965358e6132e84b1828b82e0e8b17f8ac2dec54b"><code>965358e</code></a>
refactor(linter): reuse <code>RuleNoConfig</code> reference for rule
schema (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23390">#23390</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/dd560ae213490fb208fdaa278562cf9169777f86"><code>dd560ae</code></a>
feat(linter/unicorn): implement
<code>no-array-fill-with-reference-type</code> rule (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23">#23</a>...</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/af36c2f9cd497c2f233de1902fc96b88804a2d3c"><code>af36c2f</code></a>
feat(linter): add schema for <code>react/jsx-curly-brace-presence</code>
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23400">#23400</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/47d34a304c83f9a34d78d2895c3616206f5888a1"><code>47d34a3</code></a>
feat(linter): add schema for <code>react/jsx-handler-names</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/23393">#23393</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/oxc-project/oxc/commits/oxlint_v1.70.0/npm/oxlint">compare
view</a></li>
</ul>
</details>
<br />

Updates `tailwindcss` from 4.3.0 to 4.3.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tailwindlabs/tailwindcss/releases">tailwindcss's
releases</a>.</em></p>
<blockquote>
<h2>v4.3.1</h2>
<h3>Added</h3>
<ul>
<li>Add <code>--silent</code> option to suppress output in
<code>@tailwindcss/cli</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20100">#20100</a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Remove deprecation warnings by using
<code>Module#registerHooks</code> instead of
<code>Module#register</code> on Node 26+ (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20028">#20028</a>)</li>
<li>Canonicalization: don't crash when plugin utilities throw for
unsupported values (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20052">#20052</a>)</li>
<li>Allow <code>@apply</code> to be used with CSS mixins (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/19427">#19427</a>)</li>
<li>Ensure <code>not-*</code> correctly negates <code>@container</code>
queries, including <code>style(…)</code> queries (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20059">#20059</a>)</li>
<li>Ensure <code>drop-shadow-*</code> color utilities work with custom
shadow values containing <code>calc(…)</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20080">#20080</a>)</li>
<li>Fix 'Sourcemap is likely to be incorrect' warnings when using
<code>@tailwindcss/vite</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20103">#20103</a>)</li>
<li>Ensure <code>@tailwindcss/webpack</code> can be installed in Rspack
projects without requiring <code>webpack</code> as a peer dependency (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20027">#20027</a>)</li>
<li>Canonicalization: don't suggest invalid <code>calc(…)</code>
expressions (e.g. <code>px-[calc(1rem+0px)]</code> →
<code>px-[calc(1rem+0)]</code>) (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20127">#20127</a>)</li>
<li>Canonicalization: avoid suggesting large spacing-scale values for
arbitrary lengths (e.g. <code>left-[99999px]</code> →
<code>left-[99999px]</code>, not <code>left-24999.75</code>) (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20130">#20130</a>)</li>
<li>Ensure <code>@tailwindcss/cli</code> in <code>--watch</code> mode
recovers when a tracked dependency is deleted and restored (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20137">#20137</a>)</li>
<li>Ensure standalone <code>@tailwindcss/cli</code> binaries are ignored
when scanning for class candidates (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20139">#20139</a>)</li>
<li>Ensure class candidates are extracted from Twig
<code>addClass(…)</code> and <code>removeClass(…)</code> calls (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20198">#20198</a>)</li>
<li>Don't crash in the Ruby or Vue preprocessors when scanning files
containing invalid UTF-8 bytes (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/19588">#19588</a>)</li>
<li>Allow <code>@variant</code> to be used inside <code>addBase</code>
(<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/19480">#19480</a>)</li>
<li>Ensure <code>@source</code> globs with symlinks are preserved (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20203">#20203</a>)</li>
<li>Ensure later <code>@source</code> rules can re-include files
excluded by earlier <code>@source not</code> rules (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20203">#20203</a>)</li>
<li>Upgrade: don't migrate empty class rules to invalid
<code>@utility</code> rules (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20205">#20205</a>)</li>
<li>Ensure transitions between <code>inset-shadow-none</code> and other
inset shadows work correctly (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20208">#20208</a>)</li>
<li>Ensure explicitly referenced <code>@source</code> directories are
scanned even when ignored by git (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20214">#20214</a>)</li>
<li>Ensure <code>@source</code> globs ending in <code>**/*</code>
preserve dynamic path segments to avoid scanning too many files (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20217">#20217</a>)</li>
<li>Canonicalization: don't fold <code>calc(…)</code> divisions when the
result would require high precision (e.g.
<code>w-[calc(100%/3.5)]</code> → <code>w-[calc(100%/3.5)]</code>, not
<code>w-[28.571428571428573%]</code>) (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20221">#20221</a>)</li>
<li>Serve ESM type declarations to ESM importers of
<code>@tailwindcss/postcss</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20228">#20228</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>Generate <code>0</code> instead of <code>calc(var(--spacing) *
0)</code> for spacing utilities like <code>m-0</code> and
<code>left-0</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20196">#20196</a>)</li>
<li>Generate <code>var(--spacing)</code> instead of
<code>calc(var(--spacing) * 1)</code> for spacing utilities like
<code>m-1</code> and <code>left-1</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20196">#20196</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md">tailwindcss's
changelog</a>.</em></p>
<blockquote>
<h2>[4.3.1] - 2026-06-12</h2>
<h3>Added</h3>
<ul>
<li>Add <code>--silent</code> option to suppress output in
<code>@tailwindcss/cli</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20100">#20100</a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Remove deprecation warnings by using
<code>Module#registerHooks</code> instead of
<code>Module#register</code> on Node 26+ (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20028">#20028</a>)</li>
<li>Canonicalization: don't crash when plugin utilities throw for
unsupported values (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20052">#20052</a>)</li>
<li>Allow <code>@apply</code> to be used with CSS mixins (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/19427">#19427</a>)</li>
<li>Ensure <code>not-*</code> correctly negates <code>@container</code>
queries, including <code>style(…)</code> queries (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20059">#20059</a>)</li>
<li>Ensure <code>drop-shadow-*</code> color utilities work with custom
shadow values containing <code>calc(…)</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20080">#20080</a>)</li>
<li>Fix 'Sourcemap is likely to be incorrect' warnings when using
<code>@tailwindcss/vite</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20103">#20103</a>)</li>
<li>Ensure <code>@tailwindcss/webpack</code> can be installed in Rspack
projects without requiring <code>webpack</code> as a peer dependency (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20027">#20027</a>)</li>
<li>Canonicalization: don't suggest invalid <code>calc(…)</code>
expressions (e.g. <code>px-[calc(1rem+0px)]</code> →
<code>px-[calc(1rem+0)]</code>) (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20127">#20127</a>)</li>
<li>Canonicalization: avoid suggesting large spacing-scale values for
arbitrary lengths (e.g. <code>left-[99999px]</code> →
<code>left-[99999px]</code>, not <code>left-24999.75</code>) (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20130">#20130</a>)</li>
<li>Ensure <code>@tailwindcss/cli</code> in <code>--watch</code> mode
recovers when a tracked dependency is deleted and restored (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20137">#20137</a>)</li>
<li>Ensure standalone <code>@tailwindcss/cli</code> binaries are ignored
when scanning for class candidates (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20139">#20139</a>)</li>
<li>Ensure class candidates are extracted from Twig
<code>addClass(…)</code> and <code>removeClass(…)</code> calls (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20198">#20198</a>)</li>
<li>Don't crash in the Ruby or Vue preprocessors when scanning files
containing invalid UTF-8 bytes (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/19588">#19588</a>)</li>
<li>Allow <code>@variant</code> to be used inside <code>addBase</code>
(<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/19480">#19480</a>)</li>
<li>Ensure <code>@source</code> globs with symlinks are preserved (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20203">#20203</a>)</li>
<li>Ensure later <code>@source</code> rules can re-include files
excluded by earlier <code>@source not</code> rules (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20203">#20203</a>)</li>
<li>Upgrade: don't migrate empty class rules to invalid
<code>@utility</code> rules (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20205">#20205</a>)</li>
<li>Ensure transitions between <code>inset-shadow-none</code> and other
inset shadows work correctly (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20208">#20208</a>)</li>
<li>Ensure explicitly referenced <code>@source</code> directories are
scanned even when ignored by git (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20214">#20214</a>)</li>
<li>Ensure <code>@source</code> globs ending in <code>**/*</code>
preserve dynamic path segments to avoid scanning too many files (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20217">#20217</a>)</li>
<li>Canonicalization: don't fold <code>calc(…)</code> divisions when the
result would require high precision (e.g.
<code>w-[calc(100%/3.5)]</code> → <code>w-[calc(100%/3.5)]</code>, not
<code>w-[28.571428571428573%]</code>) (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20221">#20221</a>)</li>
<li>Serve ESM type declarations to ESM importers of
<code>@tailwindcss/postcss</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20228">#20228</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>Generate <code>0</code> instead of <code>calc(var(--spacing) *
0)</code> for spacing utilities like <code>m-0</code> and
<code>left-0</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20196">#20196</a>)</li>
<li>Generate <code>var(--spacing)</code> instead of
<code>calc(var(--spacing) * 1)</code> for spacing utilities like
<code>m-1</code> and <code>left-1</code> (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20196">#20196</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/8a14a710102cae195f6811e8578bef9477bc6be9"><code>8a14a71</code></a>
4.3.1 (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20226">#20226</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/12833aa4b3a8813fd32da74be7a2fd75aa706ab7"><code>12833aa</code></a>
Fix canonicalization bug where we end up with a high precision number
(<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20221">#20221</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/97a5b3abfb75ce9722266cd323a24eee057634fc"><code>97a5b3a</code></a>
docs: fix double word 'to to' in test comment (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20216">#20216</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/d01e103cc4741c29764afaa287d2fb21356f7eea"><code>d01e103</code></a>
Add missing <code>inset</code> keyword for
<code>inset-shadow-none</code> (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20208">#20208</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/ad6693906a321912c90fd13c4a92e2cb26660985"><code>ad66939</code></a>
Allow <code>@variant</code> to be used inside <code>addBase</code> (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/19480">#19480</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/efae52c3af50300d5066596a6acbaf87e68ef9e4"><code>efae52c</code></a>
Simplify CSS when using utilities that use a <code>*-0</code> or
<code>*-1</code> value (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20196">#20196</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/6b43b6400a743a508afd712b50a73b2d3feb6444"><code>6b43b64</code></a>
Canonicalization: limit arbitrary to bare values conversion (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20130">#20130</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/d4f24c5f161b553dd99aef294ef6635093d1c440"><code>d4f24c5</code></a>
Fix invalid canonicalization where <code>0\&lt;unit&gt;</code> was
migrated to <code>0</code> (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20127">#20127</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/749c45ef26735c59a446fc5e13e96359b68b9082"><code>749c45e</code></a>
Expose <code>index</code> and <code>siblings</code> on walk context (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20109">#20109</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss/commit/8dcdb66e8a58b9df621ed1a9cb59396e0ddb6dd7"><code>8dcdb66</code></a>
Bump dependencies (<a
href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss/issues/20095">#20095</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/tailwindlabs/tailwindcss/commits/v4.3.1/packages/tailwindcss">compare
view</a></li>
</ul>
</details>
<br />

Updates `vitest` from 4.1.8 to 4.1.9
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitest-dev/vitest/releases">vitest's
releases</a>.</em></p>
<blockquote>
<h2>v4.1.9</h2>
<h3>🐞 Bug Fixes</h3>
<ul>
<li>Fix <code>importOriginal</code> with optimizer and query import
[backport to v4] - by <strong>Hiroshi Ogawa</strong>, <strong>David
Harris</strong>, <strong>Codex</strong>and <strong>Vladimir</strong> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10546">vitest-dev/vitest#10546</a>
<a href="https://github.com/vitest-dev/vitest/commit/a5180190c"><!-- raw
HTML omitted -->(a5180)<!-- raw HTML omitted --></a></li>
<li><strong>browser</strong>:
<ul>
<li>Wait for orchestrator readiness before resolving browser sessions
[backport to v4] - by <strong>Vladimir</strong> and <strong>Séamus
O'Connor</strong> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10555">vitest-dev/vitest#10555</a>
<a href="https://github.com/vitest-dev/vitest/commit/7fb29651a"><!-- raw
HTML omitted -->(7fb29)<!-- raw HTML omitted --></a></li>
<li>Wait for iframe tester readiness before preparing [backport to v4] -
by <strong>Vladimir</strong> and <strong>Séamus O'Connor</strong> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10497">vitest-dev/vitest#10497</a>
and <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10556">vitest-dev/vitest#10556</a>
<a href="https://github.com/vitest-dev/vitest/commit/fbc626c40"><!-- raw
HTML omitted -->(fbc62)<!-- raw HTML omitted --></a></li>
</ul>
</li>
<li><strong>mocker</strong>:
<ul>
<li>Hoist vi.mock() for vite-plus/test imports [backport to v4] - by
<strong>Hiroshi Ogawa</strong>, <strong>LongYinan</strong>,
<strong>Claude Opus 4.8</strong> and <strong>Vladimir</strong> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10548">vitest-dev/vitest#10548</a>
<a href="https://github.com/vitest-dev/vitest/commit/2c9559c02"><!-- raw
HTML omitted -->(2c955)<!-- raw HTML omitted --></a></li>
</ul>
</li>
<li><strong>pool</strong>:
<ul>
<li>Prevent test run hang on worker crash [backport to v4] - by
<strong>Ari Perkkiö</strong> and <strong>Jattioui Ismail</strong> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10543">vitest-dev/vitest#10543</a>
and <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10564">vitest-dev/vitest#10564</a>
<a href="https://github.com/vitest-dev/vitest/commit/934b0f587"><!-- raw
HTML omitted -->(934b0)<!-- raw HTML omitted --></a></li>
</ul>
</li>
</ul>
<h5><a
href="https://github.com/vitest-dev/vitest/compare/v4.1.8...v4.1.9">View
changes on GitHub</a></h5>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/vitest-dev/vitest/commit/a7a61e78c7d0718f00173cff6800a91a344457d4"><code>a7a61e7</code></a>
chore: release v4.1.9 (<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/10598">#10598</a>)</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/934b0f587cb61d8338d83f525295322692a2db40"><code>934b0f5</code></a>
fix(pool): prevent test run hang on worker crash (<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/10543">#10543</a>)
[backport to v4] (#...</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/7fb29651afbae2a9b0cefe6c031a9308f168ac60"><code>7fb2965</code></a>
fix(browser): wait for orchestrator readiness before resolving browser
sessio...</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/a5180190c1be7089e3705e3dd9e84fea118d09d3"><code>a518019</code></a>
fix: fix <code>importOriginal</code> with optimizer and query import
[backport to v4] (#...</li>
<li>See full diff in <a
href="https://github.com/vitest-dev/vitest/commits/v4.1.9/packages/vitest">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: CaliBrain <calibrain@l4n.xyz>
2026-06-21 00:58:18 -04:00
CaliBrain 61b3eaf351 Fix internal bypasser using system DNS in subprocess, breaking downloads behind ISP DNS blocks (#1028) (#1080) 2026-06-21 00:42:03 -04:00
dependabot[bot] 7158b79146 Bump the python-deps group with 3 updates (#1074)
Bumps the python-deps group with 3 updates:
[python-socketio](https://github.com/miguelgrinberg/python-socketio),
[seleniumbase](https://github.com/seleniumbase/SeleniumBase) and
[prek](https://github.com/j178/prek).

Updates `python-socketio` from 5.16.2 to 5.16.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/miguelgrinberg/python-socketio/releases">python-socketio's
releases</a>.</em></p>
<blockquote>
<h2>Release 5.16.3</h2>
<p>See <a
href="https://github.com/miguelgrinberg/python-socketio/blob/main/CHANGES.md">CHANGES.md</a>
for release notes.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/miguelgrinberg/python-socketio/blob/main/CHANGES.md">python-socketio's
changelog</a>.</em></p>
<blockquote>
<h1>python-socketio change log</h1>
<p><strong>Release 5.16.3</strong> - 2026-06-15</p>
<ul>
<li>Catch all exceptions in redis and rabbitmq client managers <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1581">#1581</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/80bb5c9b07e6b53e45f610ec29a24124c539d41b">commit</a>)</li>
</ul>
<p><strong>Release 5.16.2</strong> - 2026-05-21</p>
<ul>
<li>Prevent unnecessary resource allocations <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1574">#1574</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/ca140fe44d0ceb3004073645222abec182d8784b">commit</a>)</li>
<li>Add zizmor to CI builds <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1570">#1570</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/664dc27ec6f34179ed1724430eda3520627fc642">commit</a>)</li>
</ul>
<p><strong>Release 5.16.1</strong> - 2026-02-06</p>
<ul>
<li>Use configured JSON module in managers <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1549">#1549</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/6229261ae6e8c01e675097242e333ee84587a544">commit</a>)</li>
<li>Admin UI fixes: remove duplicate tasks, report transport upgrades
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/1c2eab13a92fac9e43663eb0b5f099eb1c40ea5b">commit</a>)</li>
<li>Switch to Furo documentation template (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/add47d8c7abca697a2804141bbf29bfb095f7d5e">commit</a>)</li>
<li>Add Python free-threading to CI <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1554">#1554</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/ccdd2004a038ae4b8171a05120c5d0787332f7ee">commit</a>)</li>
</ul>
<p><strong>Release 5.16.0</strong> - 2025-12-24</p>
<ul>
<li>Address deprecation warnings (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/b235699d9b06564753c570b76055997e9d62a938">commit</a>)</li>
<li>Drop Python 3.8 and 3.9 from CI builds (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/d0728d2f74538762dd551fa9cd0cd1fd5aedfa37">commit</a>)</li>
</ul>
<p><strong>Release 5.15.1</strong> - 2025-12-16</p>
<ul>
<li>Restore support multiple arguments via pubsub emits <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1540">#1540</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/c279f26bb8c9887c4ca99d4d81ad331c4844438c">commit</a>)</li>
</ul>
<p><strong>Release 5.15.0</strong> - 2025-11-22</p>
<ul>
<li>Retry initial Redis connection <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1536">#1534</a>
([commit <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1">#1</a>](<a
href="https://github.com/miguelgrinberg/python-socketio/commit/1e903e173a2d7b04599c4f7f9630c1abbb531fad">https://github.com/miguelgrinberg/python-socketio/commit/1e903e173a2d7b04599c4f7f9630c1abbb531fad</a>)
[commit <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/2">#2</a>](<a
href="https://github.com/miguelgrinberg/python-socketio/commit/5e898a9b93526e6e667767e54c60f4c84589989d">https://github.com/miguelgrinberg/python-socketio/commit/5e898a9b93526e6e667767e54c60f4c84589989d</a>))</li>
<li>Correctly regenerate RabbitMQ binding after a connection failure <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1516">#1516</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/c52e93b4a328d98a968bfbdec0cfd598b73ee913">commit</a>)
(thanks <strong>Gritty_dev</strong>!)</li>
<li>Support <code>ext_type</code> in the <code>MsgPackPacket</code>
class <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1521">#1521</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/208925344a48485d2cd56e40eb74266c3bcb5311">commit</a>)</li>
<li>Support sending <code>bytesarray</code>s when using pub/sub managers
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/6c9b9974f72e2efdf62407ecab24ee6995448098">commit</a>)</li>
<li>Fix typos in documentation <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1520">#1520</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/db3f1c2a0105c30cb833ddfca8f05fe4320468fd">commit</a>)
(thanks <strong>Lê Nam Khánh</strong>!)</li>
<li>Improvements to the logging documentation (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/b423d0e38eef559b7e81acb7e32059de305f982c">commit</a>)</li>
</ul>
<p><strong>Release 5.14.3</strong> - 2025-10-29</p>
<ul>
<li>Support Python's native <code>ConnectionRefusedError</code>
exception to reject a connection <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1515">#1515</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/f3b18bde3f16437b223491d4c3e440ea37105fe3">commit</a>)</li>
<li>Push binary data to the aiopika client manager <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1514">#1514</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/194e1b7f277b5f72e1de78d3f614e7b8b6c788ac">commit</a>)</li>
</ul>
<p><strong>Release 5.14.2</strong> - 2025-10-15</p>
<ul>
<li>Restore binary message support in message queue setups <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1509">#1509</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/bab4a10f48aaae11d7f832ebe5c30ad3f85d31b3">commit</a>)</li>
<li>Fix formatting of client connection error <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1507">#1507</a>
(<a
href="https://github.com/miguelgrinberg/python-socketio/commit/f298c9b54d76ab09ff72935937e1b9575bc45ffd">commit</a>)</li>
<li>Add 3.14 and pypy-3.11 CI tasks (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/1f4cd3b025c294f25208ec3c05b5f8df6209e403">commit</a>)</li>
<li>Improve documentation of the
<code>BaseManager.get_participants()</code> method (<a
href="https://github.com/miguelgrinberg/python-socketio/commit/33722a0d96036f005188b07b8b46a5ef091fe65f">commit</a>)</li>
</ul>
<p><strong>Release 5.14.1</strong> - 2025-10-02</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/36c550f62415dc9c9c8dd39203b3c59ec7bdcdea"><code>36c550f</code></a>
Release 5.16.3</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/80bb5c9b07e6b53e45f610ec29a24124c539d41b"><code>80bb5c9</code></a>
Catch all exceptionsin redis and rabbitmq managers (Fixes <a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1581">#1581</a>)</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/3f1d50903d1034fd9ffeb2110465ee0c1f90ea4c"><code>3f1d509</code></a>
Bump aiohttp from 3.13.4 to 3.14.0 in /examples/server/aiohttp (<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1580">#1580</a>)
#nolog</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/c2918c1b43489bde34d9198352bc1a4667021fc0"><code>c2918c1</code></a>
Bump qs and express in /examples/server/javascript (<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1579">#1579</a>)
#nolog</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/e19cb7df63e132da3a9b599e5bf948199c6a524f"><code>e19cb7d</code></a>
Bump qs and express in /examples/client/javascript (<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1578">#1578</a>)
#nolog</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/f616fb1cb4ba766bac99b05553672e8d3de90b48"><code>f616fb1</code></a>
Bump ws, engine.io, socket.io-adapter and socket.io-client (<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1575">#1575</a>)
#nolog</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/5f35b0bf5bb959b0a0b0d5006040a53112b50633"><code>5f35b0b</code></a>
Bump ws, engine.io, socket.io-adapter and socket.io-client (<a
href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1576">#1576</a>)
#nolog</li>
<li><a
href="https://github.com/miguelgrinberg/python-socketio/commit/995f0807e78207ec88872764f7533a115fb9c6ba"><code>995f080</code></a>
Version 5.16.3.dev0</li>
<li>See full diff in <a
href="https://github.com/miguelgrinberg/python-socketio/compare/v5.16.2...v5.16.3">compare
view</a></li>
</ul>
</details>
<br />

Updates `seleniumbase` from 4.49.13 to 4.49.14
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.49.14 - Chromium overhaul with CDP Mode updates</h2>
<h2>Chromium overhaul with CDP Mode updates</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/cb03afab9f8aeb67273348a8f5641faaf8ed90e7">Fix
issue that caused Chromium 151 to crash at startup</a>
-- This resolves <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4393">seleniumbase/SeleniumBase#4393</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/14d688b69f065f51d5b7b5d87059a591ad0a59d2">Add
option for setting the exact Chromium revision to download</a>
-- This resolves <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4394">seleniumbase/SeleniumBase#4394</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/3c729af379fffaf2365782ea400d57d8f01e75a1">Update
Chromium options</a>
-- This resolves <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4395">seleniumbase/SeleniumBase#4395</a>
-- this resolves <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4396">seleniumbase/SeleniumBase#4396</a></li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/f01f59153b338246ec6e78e589b00463107542ad">Update
CDP Mode</a></li>
</ul>
<h3>Here's an example of downloading a custom Chromium revision:</h3>
<pre lang="zsh"><code>sbase get chromium --revision=1639046
<p>*** Chromium revision to download = 1639046</p>
<p>Downloading chrome-mac.zip from:
<a
href="https://storage.googleapis.com/chromium-browser-snapshots/Mac_Arm/1639046/chrome-mac.zip">https://storage.googleapis.com/chromium-browser-snapshots/Mac_Arm/1639046/chrome-mac.zip</a>
...
Download Complete!</p>
<p>... [UNZIPPING] ...</p>
<p>Unzip Complete!</p>
<p>Chromium was saved inside:
..../seleniumbase/drivers/chromium_drivers/
chrome-mac
</code></pre></p>
<h2>What's Changed</h2>
<ul>
<li>Chromium overhaul with CDP Mode updates by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4397">seleniumbase/SeleniumBase#4397</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.49.13...v4.49.14">https://github.com/seleniumbase/SeleniumBase/compare/v4.49.13...v4.49.14</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/dd5454faf2ba9950da5390d4dd121d17bb3b7f2c"><code>dd5454f</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4397">#4397</a>
from seleniumbase/chromium-overhaul-with-cdp-updates</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/520c0b406a784c48be94ed6dbdcc85dbd469c6dd"><code>520c0b4</code></a>
Version 4.49.14</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/0e11a764a656f1bfc65998dafa3f7a4eed96104b"><code>0e11a76</code></a>
Update the docs</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/b1901439ba8eb54dadc1a87816216bddf24b9e0e"><code>b190143</code></a>
Update examples</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/f01f59153b338246ec6e78e589b00463107542ad"><code>f01f591</code></a>
Update CDP Mode</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/3c729af379fffaf2365782ea400d57d8f01e75a1"><code>3c729af</code></a>
Update Chromium options</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/14d688b69f065f51d5b7b5d87059a591ad0a59d2"><code>14d688b</code></a>
Add option for setting the exact Chromium revision to download</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/cb03afab9f8aeb67273348a8f5641faaf8ed90e7"><code>cb03afa</code></a>
Fix issue that caused Chromium 151 to crash at startup</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/d76b2fd98f4c71a930b8b0536235cd9e7118d9f4"><code>d76b2fd</code></a>
Update CDP Mode examples</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/09fd1f35d284965ddf8a4b245710d9acee6a6e3e"><code>09fd1f3</code></a>
Add/update examples of DataDome Slider CAPTCHA-bypass</li>
<li>See full diff in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.49.13...v4.49.14">compare
view</a></li>
</ul>
</details>
<br />

Updates `prek` from 0.4.4 to 0.4.5
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/releases">prek's
releases</a>.</em></p>
<blockquote>
<h2>0.4.5</h2>
<h2>Release Notes</h2>
<p>Released on 2026-06-15.</p>
<h3>✨Highlights</h3>
<p>0.4.5 completes <code>prek</code>'s pre-commit language coverage.
With R, Conda, Perl,
and coursier now supported, <code>prek</code> supports every language
available in
pre-commit, plus <code>prek</code>-only modern languages such as Bun and
Deno.</p>
<p>This is full feature parity with pre-commit. The last major
compatibility gap
is closed, and <code>prek</code> is now on the path to 1.0.</p>
<h3>💚 Sponsorship</h3>
<p>If <code>prek</code> saves time for you or your team, please consider
sponsoring the
project on <a href="https://github.com/sponsors/j178">GitHub
Sponsors</a>. It helps keep
new features, performance work, and maintenance moving.</p>
<h3>Enhancements</h3>
<ul>
<li>Add <code>Perl</code> language support (<a
href="https://redirect.github.com/j178/prek/pull/2201">#2201</a>)</li>
<li>Add <code>R</code> language support (<a
href="https://redirect.github.com/j178/prek/pull/2206">#2206</a>)</li>
<li>Add <code>Conda</code> language support (<a
href="https://redirect.github.com/j178/prek/pull/2203">#2203</a>)</li>
<li>Add <code>coursier</code> language support (<a
href="https://redirect.github.com/j178/prek/pull/2194">#2194</a>)</li>
<li>Add group filters to list (<a
href="https://redirect.github.com/j178/prek/pull/2152">#2152</a>)</li>
<li>Remove legacy repo path support (<a
href="https://redirect.github.com/j178/prek/pull/2150">#2150</a>)</li>
<li>Skip cloning fully filtered remote repos (<a
href="https://redirect.github.com/j178/prek/pull/2151">#2151</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Avoid building JSON trees in syntax hooks (<a
href="https://redirect.github.com/j178/prek/pull/2156">#2156</a>)</li>
<li>Scan mixed line endings without line vector (<a
href="https://redirect.github.com/j178/prek/pull/2160">#2160</a>)</li>
<li>Skip file scan for always-run no-filename hooks (<a
href="https://redirect.github.com/j178/prek/pull/2155">#2155</a>)</li>
<li>Use fixed buffer for EOF scan (<a
href="https://redirect.github.com/j178/prek/pull/2163">#2163</a>)</li>
<li>Use fixed buffer for private key scan (<a
href="https://redirect.github.com/j178/prek/pull/2164">#2164</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Fix pretty-format-json CRLF handling (<a
href="https://redirect.github.com/j178/prek/pull/2210">#2210</a>)</li>
<li>Handle missing blobs in diff snapshots (<a
href="https://redirect.github.com/j178/prek/pull/2211">#2211</a>)</li>
<li>Ignore coursier channel from working repo (<a
href="https://redirect.github.com/j178/prek/pull/2202">#2202</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/j178"><code>@​j178</code></a></li>
</ul>
<h2>Install prek 0.4.5</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/j178/prek/blob/master/CHANGELOG.md">prek's
changelog</a>.</em></p>
<blockquote>
<h2>0.4.5</h2>
<p>Released on 2026-06-15.</p>
<h3>✨Highlights</h3>
<p>0.4.5 completes <code>prek</code>'s pre-commit language coverage.
With R, Conda, Perl,
and coursier now supported, <code>prek</code> supports every language
available in
pre-commit, plus <code>prek</code>-only modern languages such as Bun and
Deno.</p>
<p>This is full feature parity with pre-commit. The last major
compatibility gap
is closed, and <code>prek</code> is now on the path to 1.0.</p>
<h3>💚 Sponsorship</h3>
<p>If <code>prek</code> saves time for you or your team, please consider
sponsoring the
project on <a href="https://github.com/sponsors/j178">GitHub
Sponsors</a>. It helps keep
new features, performance work, and maintenance moving.</p>
<h3>Enhancements</h3>
<ul>
<li>Add <code>Perl</code> language support (<a
href="https://redirect.github.com/j178/prek/pull/2201">#2201</a>)</li>
<li>Add <code>R</code> language support (<a
href="https://redirect.github.com/j178/prek/pull/2206">#2206</a>)</li>
<li>Add <code>Conda</code> language support (<a
href="https://redirect.github.com/j178/prek/pull/2203">#2203</a>)</li>
<li>Add <code>coursier</code> language support (<a
href="https://redirect.github.com/j178/prek/pull/2194">#2194</a>)</li>
<li>Add group filters to list (<a
href="https://redirect.github.com/j178/prek/pull/2152">#2152</a>)</li>
<li>Remove legacy repo path support (<a
href="https://redirect.github.com/j178/prek/pull/2150">#2150</a>)</li>
<li>Skip cloning fully filtered remote repos (<a
href="https://redirect.github.com/j178/prek/pull/2151">#2151</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Avoid building JSON trees in syntax hooks (<a
href="https://redirect.github.com/j178/prek/pull/2156">#2156</a>)</li>
<li>Scan mixed line endings without line vector (<a
href="https://redirect.github.com/j178/prek/pull/2160">#2160</a>)</li>
<li>Skip file scan for always-run no-filename hooks (<a
href="https://redirect.github.com/j178/prek/pull/2155">#2155</a>)</li>
<li>Use fixed buffer for EOF scan (<a
href="https://redirect.github.com/j178/prek/pull/2163">#2163</a>)</li>
<li>Use fixed buffer for private key scan (<a
href="https://redirect.github.com/j178/prek/pull/2164">#2164</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Fix pretty-format-json CRLF handling (<a
href="https://redirect.github.com/j178/prek/pull/2210">#2210</a>)</li>
<li>Handle missing blobs in diff snapshots (<a
href="https://redirect.github.com/j178/prek/pull/2211">#2211</a>)</li>
<li>Ignore coursier channel from working repo (<a
href="https://redirect.github.com/j178/prek/pull/2202">#2202</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/j178"><code>@​j178</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/j178/prek/commit/bb3108283fd4aab0d9eb59f23098e0c05a96cdbe"><code>bb31082</code></a>
Update README.md</li>
<li><a
href="https://github.com/j178/prek/commit/fa4e2dc060eccac3c2521ec7bf4af1272c5d6205"><code>fa4e2dc</code></a>
Bump version to 0.4.5 (<a
href="https://redirect.github.com/j178/prek/issues/2221">#2221</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/e9336fa724b8a99b3a21fd60eb0849cd731153ef"><code>e9336fa</code></a>
Add R language support (<a
href="https://redirect.github.com/j178/prek/issues/2206">#2206</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/97a5a15701d3691287e4f969a2a2f421d1501a46"><code>97a5a15</code></a>
Update Rust crate which to v8.0.3 (<a
href="https://redirect.github.com/j178/prek/issues/2219">#2219</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/20a519aa2aa4add7e70dfbb2ff743cd1f35e25f3"><code>20a519a</code></a>
Update Rust crate ignore to v0.4.26 (<a
href="https://redirect.github.com/j178/prek/issues/2218">#2218</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/88f0e2bd8a7d02be7fb19fcd17e1e8db79b8b0bc"><code>88f0e2b</code></a>
Update dependency uv to v0.11.19 (<a
href="https://redirect.github.com/j178/prek/issues/2214">#2214</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/7bd43000679b33e2b947f453436237dae062af70"><code>7bd4300</code></a>
Update GitHub Actions to v7 (<a
href="https://redirect.github.com/j178/prek/issues/2220">#2220</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/48ed5cc2838066d71278dd887dee595c32085be2"><code>48ed5cc</code></a>
Update Rust crate async_zip to 0.0.18 (<a
href="https://redirect.github.com/j178/prek/issues/2217">#2217</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/257e98a429e96f89a4d965ad28568cba77ca33c2"><code>257e98a</code></a>
Update pre-commit to v1.47.2 (<a
href="https://redirect.github.com/j178/prek/issues/2216">#2216</a>)</li>
<li><a
href="https://github.com/j178/prek/commit/0c0460241a13b9ac519e1abd3a0a8a3ad60d4d2f"><code>0c04602</code></a>
Update GitHub Actions (<a
href="https://redirect.github.com/j178/prek/issues/2215">#2215</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/j178/prek/compare/v0.4.4...v0.4.5">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-21 00:00:30 -04:00
dependabot[bot] 3c1b79989a Bump node from fb71d01 to 156b55f in the docker-base-image-digests group (#1075)
Bumps the docker-base-image-digests group with 1 update: node.

Updates `node` from `fb71d01` to `156b55f`


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=node&package-manager=docker&previous-version=24-alpine&new-version=24-alpine)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-21 00:00:22 -04:00
dependabot[bot] e41c1d4e8e Bump python from 3.14.5-slim to 3.14.6-slim (#1076)
Bumps python from 3.14.5-slim to 3.14.6-slim.


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=python&package-manager=docker&previous-version=3.14.5-slim&new-version=3.14.6-slim)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-21 00:00:13 -04:00
Hrachya ShaginyanandZo Bot ec7e3482bb use a stable hash for the Booklore options cache key (#1079)
`_get_booklore_cache_key` returned
`f"{base_url}|{username}|{hash(password)}"`. Python's built-in `hash()`
is randomized per process (`PYTHONHASHSEED`), so the cache key for the
same Booklore credentials changed between every worker process and every
restart. If the dashboard was opened by a worker that landed on a
different seed than the worker that warmed the cache, the
`_BOOKLORE_OPTIONS_CACHE.get("key") == cache_key` check would miss on
every lookup, forcing a fresh `booklore_login` API call each time.

Switch to `hashlib.sha256(password.encode()).hexdigest()` —
deterministic across processes and restarts. SHA-256 is overkill for a
cache key, but matching the existing convention (`hashlib.sha1` is used
elsewhere in the codebase) is what we want here.

The password is already in memory (it comes from the request body /
`config.get`), so this doesn't introduce a new secret-handling concern.

Co-authored-by: Zo Bot <github-automation@zo.computer>
2026-06-20 23:59:56 -04:00
dependabot[bot] 05cd384efc Bump the python-deps group with 3 updates (#1073)
Bumps the python-deps group with 3 updates:
[seleniumbase](https://github.com/seleniumbase/SeleniumBase),
[basedpyright](https://github.com/detachhead/basedpyright) and
[pytest](https://github.com/pytest-dev/pytest).

Updates `seleniumbase` from 4.49.12 to 4.49.13
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.49.13 - Refresh Python dependencies</h2>
<h2>Refresh Python dependencies</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/1cfc9c241fa4e8065a61ea1124f374257e464af4">Refresh
Python dependencies</a>
-- Refresh <code>filelock</code>, <code>pytest</code>, and
<code>pyotp</code>.</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>Refresh Python dependencies by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4390">seleniumbase/SeleniumBase#4390</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.49.12...v4.49.13">https://github.com/seleniumbase/SeleniumBase/compare/v4.49.12...v4.49.13</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/daa6847905136b1e62a44837a1a03c7b3ca415d2"><code>daa6847</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4390">#4390</a>
from seleniumbase/refresh-python-dependencies</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/07722ce8bb401174ac1b7d3e0bfc3a476ea2d4d8"><code>07722ce</code></a>
Version 4.39.13</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/1cfc9c241fa4e8065a61ea1124f374257e464af4"><code>1cfc9c2</code></a>
Refresh Python dependencies</li>
<li>See full diff in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.49.12...v4.49.13">compare
view</a></li>
</ul>
</details>
<br />

Updates `basedpyright` from 1.39.7 to 1.39.8
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/fab7323c03bfa17db61cf63457beee9e484fde80"><code>fab7323</code></a>
1.39.8</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/c685372fd5da030a7c922b65ef8ecfd1e1cf7265"><code>c685372</code></a>
Avoid auto f-string conversion for raw strings</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/a55c2a4ddc65230bde700c32677d62e4e7c6586b"><code>a55c2a4</code></a>
docs: document typeCheckingMode off behavior</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/61b9817de80a927395ce2315a2e34c663ac43664"><code>61b9817</code></a>
fix: clarify implicit abstract class diagnostic</li>
<li><a
href="https://github.com/DetachHead/basedpyright/commit/7dd1aafafd74d3c9c07a2a287295c5dafa073f38"><code>7dd1aaf</code></a>
fix default value in <code>pythonPlatform</code> documentation, which
differs from upstream</li>
<li>See full diff in <a
href="https://github.com/detachhead/basedpyright/compare/v1.39.7...v1.39.8">compare
view</a></li>
</ul>
</details>
<br />

Updates `pytest` from 9.0.3 to 9.1.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pytest-dev/pytest/releases">pytest's
releases</a>.</em></p>
<blockquote>
<h2>9.1.0</h2>
<h1>pytest 9.1.0 (2026-06-13)</h1>
<h2>Removals and backward incompatible breaking changes</h2>
<ul>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14533">#14533</a>:
When using <code>--doctest-modules</code>, autouse fixtures with
<code>module</code>, <code>package</code> or <code>session</code> scope
that are defined inline in Python test modules (not plugins or
conftests) will now possibly execute twice.</p>
<p>If this is undesirable, move the fixture definition to a
<code>conftest.py</code> file if possible.</p>
<p>Technical explanation for those interested:
When using <!-- raw HTML omitted -->--doctest-modules<!-- raw HTML
omitted -->, pytest possibly collects Python modules twice, once as
<code>pytest.Module</code> and once as a <code>DoctestModule</code>
(depending on the configuration).
Due to improvements in pytest's fixture implementation, if e.g. the
<code>DoctestModule</code> collects a fixture, it is now visible to it
only, and not to the <code>Module</code>.
This means that both need to register the fixtures independently.</p>
</li>
</ul>
<h2>Deprecations (removal in next major release)</h2>
<ul>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/10819">#10819</a>:
Added a deprecation warning for class-scoped fixtures defined as
instance methods (without <code>@classmethod</code>). Such fixtures set
attributes on a different instance than the test methods use, leading to
unexpected behavior. Use <code>@classmethod</code> decorator instead --
by <code>yastcher</code>.</p>
<p>See <code>10819</code> and <code>14011</code>.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/12882">#12882</a>:
Calling <code>request.getfixturevalue()
&lt;pytest.FixtureRequest.getfixturevalue&gt;</code> during teardown to
request a fixture that was not already requested is now deprecated and
will become an error in pytest 10.</p>
<p>See <code>dynamic-fixture-request-during-teardown</code> for
details.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13409">#13409</a>:
Using non-<code>~collections.abc.Collection</code> iterables (such as
generators, iterators, or custom iterable objects) for the
<code>argvalues</code> parameter in <code>@pytest.mark.parametrize
&lt;pytest.mark.parametrize ref&gt;</code> and
<code>metafunc.parametrize &lt;pytest.Metafunc.parametrize&gt;</code> is
now deprecated.</p>
<p>These iterables get exhausted after the first iteration,
leading to tests getting unexpectedly skipped in cases such as running
<code>pytest.main()</code> multiple times,
using class-level parametrize decorators,
or collecting tests multiple times.</p>
<p>See <code>parametrize-iterators</code> for details and
suggestions.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13946">#13946</a>:
The private <code>config.inicfg</code> attribute is now deprecated.
Use <code>config.getini() &lt;pytest.Config.getini&gt;</code> to access
configuration values instead.</p>
<p>See <code>config-inicfg</code> for more details.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14004">#14004</a>:
Passing <code>baseid</code> to <code>~pytest.FixtureDef</code> or
<code>nodeid</code> strings to fixture registration APIs is now
deprecated. These are internal pytest APIs that are used by some
plugins.</p>
<p>Use the <code>node</code> parameter instead for fixture scoping. This
enables more robust node-based
matching instead of string prefix matching.
If you've used <code>nodeid=None</code>, pass <code>node=session</code>
instead.</p>
<p>This will be removed in pytest 10.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14335">#14335</a>:
The method of configuring hooks using markers, deprecated since pytest
7.2, is now scheduled to be removed in pytest 10.
See <code>hook-markers</code> for more details.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14434">#14434</a>:
The <code>--pastebin</code> option is now deprecated.</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pytest-dev/pytest/commit/b2522cf0b11fb33ecc1f4895fa1dffbb9252a63d"><code>b2522cf</code></a>
Prepare release version 9.1.0</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/368d2fca78e86ac79ec269bb078fcb1259a94fed"><code>368d2fc</code></a>
[refactor] Tighten <code>SetComparisonFunction</code> to
<code>Iterator[str]</code> (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14587">#14587</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/ff77cd8b66b43a88c26ca54384bbcab72d079497"><code>ff77cd8</code></a>
[refactor] Make base assertion comparisons return an iterator instead of
a li...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/0d8491a4ecf971800de0479ef55c7f5292c54937"><code>0d8491a</code></a>
build(deps): Bump actions/stale from 10.2.0 to 10.3.0</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/4a809d9c892f6abb5ba92b77b06f1dd878f4660a"><code>4a809d9</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14568">#14568</a>
from pytest-dev/register-fixture</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/5dfa38541becfb77d0f52cac4cc8cce71849ab61"><code>5dfa385</code></a>
Fix recursion traceback test to cover all styles (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14582">#14582</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/f52ff0c1778c15038cf2bbb00b7668dac674cc26"><code>f52ff0c</code></a>
Add <code>pytest.register_fixture</code></li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/a8ac094e80df788aec844794170b126eab0be7a4"><code>a8ac094</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14567">#14567</a>
from pytest-dev/more-visibility-deprecate</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/e5620cd21ec62f5a5f9a5141a3c76fb3953729b6"><code>e5620cd</code></a>
[pre-commit.ci] pre-commit autoupdate (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14577">#14577</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/2ce9c6d94eb691ea4da7f91f330602cbb67a6daf"><code>2ce9c6d</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14540">#14540</a>
from minbang930/fix-14533-doctest-module-fixtures</li>
<li>Additional commits viewable in <a
href="https://github.com/pytest-dev/pytest/compare/9.0.3...9.1.0">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-18 00:29:33 -04:00
dependabot[bot] 880cc4a299 Bump seleniumbase from 4.49.10 to 4.49.12 in the python-deps group (#1070)
Bumps the python-deps group with 1 update:
[seleniumbase](https://github.com/seleniumbase/SeleniumBase).

Updates `seleniumbase` from 4.49.10 to 4.49.12
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seleniumbase/SeleniumBase/releases">seleniumbase's
releases</a>.</em></p>
<blockquote>
<h2>4.49.12 - CDP Mode: Patch 116</h2>
<h2>CDP Mode: Patch 116</h2>
<ul>
<li><a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4389/commits/f87cf4f13a0e19fc360d7e8b98037dc79ec64da1">Add
missing await for async method call in CDP Mode</a>
-- This resolves <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4388">seleniumbase/SeleniumBase#4388</a>
-- (Applies to setting <code>ad_block=True</code> in CDP Mode)</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>CDP Mode: Patch 116 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4389">seleniumbase/SeleniumBase#4389</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.49.11...v4.49.12">https://github.com/seleniumbase/SeleniumBase/compare/v4.49.11...v4.49.12</a></p>
<h2>4.49.11 - CDP Mode: Patch 115</h2>
<h2>CDP Mode: Patch 115</h2>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/4207eebedeb32eb2d7c13145c70d50680b6f5fbb">Update
MyCDP</a>
-- (Note that all CDP calls go through <code>MyCDP</code>)</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/d2a57ef2c4a2a8b706093c588c7e9da78782611f">Update
CDP Mode</a>
-- Added a custom message option for all assertions
-- Added more time for the ad-blocker to take effect</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>CDP Mode: Patch 115 by <a
href="https://github.com/mdmintz"><code>@​mdmintz</code></a> in <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/pull/4385">seleniumbase/SeleniumBase#4385</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.49.10...v4.49.11">https://github.com/seleniumbase/SeleniumBase/compare/v4.49.10...v4.49.11</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/18b058837f4645588ec76eb859f57104aca78b95"><code>18b0588</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4389">#4389</a>
from seleniumbase/cdp-mode-patch-116</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/7a71fea3f9d3419cfbe7dff7d630b59d53851ac2"><code>7a71fea</code></a>
Version 4.49.12</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/f87cf4f13a0e19fc360d7e8b98037dc79ec64da1"><code>f87cf4f</code></a>
Add missing <code>await</code> for async method call</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/c9b450433a5750426a1143250ad7e0c24ed3ffa4"><code>c9b4504</code></a>
Merge pull request <a
href="https://redirect.github.com/seleniumbase/SeleniumBase/issues/4385">#4385</a>
from seleniumbase/cdp-mode-patch-115</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/daefe6f4ff1bf20408ba666a1f07347a81a9f6b8"><code>daefe6f</code></a>
Version 4.49.11</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/fc721de65c74b42bca13b89bec411aa90ffe55bb"><code>fc721de</code></a>
Update CDP Mode examples</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/eb063047cc6e8ffd4a7fca8ff3b07701d8b16037"><code>eb06304</code></a>
Update an optional Python dependency</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/4207eebedeb32eb2d7c13145c70d50680b6f5fbb"><code>4207eeb</code></a>
Update MyCDP</li>
<li><a
href="https://github.com/seleniumbase/SeleniumBase/commit/d2a57ef2c4a2a8b706093c588c7e9da78782611f"><code>d2a57ef</code></a>
Update CDP Mode</li>
<li>See full diff in <a
href="https://github.com/seleniumbase/SeleniumBase/compare/v4.49.10...v4.49.12">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=seleniumbase&package-manager=uv&previous-version=4.49.10&new-version=4.49.12)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-17 14:26:40 -04:00
390 changed files with 42077 additions and 4286 deletions
+19 -3
View File
@@ -46,9 +46,7 @@ updates:
# pre-release filter is bypassed for *grouped* updates
# (dependabot-core#9496), so a grouped python update proposes pre-release
# tags like python:3.15.0b2 as if they were a normal stable minor bump.
# Updated individually, python is filtered correctly: alpha/beta/rc tags
# are skipped and only stable releases (e.g. 3.15.0 once final) are
# proposed. node + uv stay grouped into a single digest PR.
# node + uv stay grouped into a single digest PR.
patterns: ["*"]
exclude-patterns: ["python"]
ignore:
@@ -58,6 +56,24 @@ updates:
- dependency-name: "node"
update-types: ["version-update:semver-major"]
# Python: block minor/major bumps. Ungrouping python (above) is NOT enough
# to keep pre-releases out — dependabot-core#13815 rewrote the Docker
# pre-release heuristic to catch PEP 440 tags like 3.15.0a2 / 3.5.0b3, but
# the suffixed real tag still slipped through as PR #1169
# (python:3.14.6-slim -> python:3.15.0b3-slim). CPython spells
# pre-releases without a separator, so tag parsing reads 3.15.0b3 as an
# ordinary version that sorts above 3.14.6.
#
# A minor-version ignore blocks it regardless of spelling. Patch bumps
# (3.14.6 -> 3.14.7) and same-tag digest refreshes still land automatically.
# Moving the runtime to a new Python minor is a manual, deliberate change:
# bump the tag here and confirm C-extension wheels (greenlet/gevent) exist
# for it — a source build against a pre-release ABI boots an app that binds
# its port but never serves, which wedges e2e for the full 6h job limit.
- dependency-name: "python"
update-types:
["version-update:semver-major", "version-update:semver-minor"]
# GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
@@ -3,10 +3,8 @@ on:
push:
tags:
- 'v*'
schedule:
# Nightly at 03:17 UTC — only builds if there are new commits on main
# since the last successful run (see check-changes job).
- cron: '17 3 * * *'
# Also dispatched on main by dev-image-debounce.yml, once main has been
# quiet for an hour, to publish the dev image.
workflow_dispatch:
permissions: read-all
@@ -14,41 +12,7 @@ env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository_owner }}/shelfmark
jobs:
check-changes:
runs-on: ubuntu-latest
outputs:
should_build: ${{ steps.check.outputs.should_build }}
steps:
- name: Check for new commits since last successful build
id: check
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
EVENT_NAME: ${{ github.event_name }}
CURRENT_SHA: ${{ github.sha }}
REPO: ${{ github.repository }}
run: |
# Always build on tag pushes and manual dispatch.
if [[ "$EVENT_NAME" != "schedule" ]]; then
echo "Event is $EVENT_NAME — building unconditionally."
echo "should_build=true" >> "$GITHUB_OUTPUT"
exit 0
fi
# Scheduled run: only build if HEAD differs from the last successful build on main.
LAST_SHA=$(gh api "/repos/${REPO}/actions/workflows/build-and-publish-docker-image.yml/runs?branch=main&status=success&per_page=1" --jq '.workflow_runs[0].head_sha' 2>/dev/null || true)
echo "Last successful build SHA: ${LAST_SHA:-<none>}"
echo "Current HEAD SHA: ${CURRENT_SHA}"
if [[ -z "$LAST_SHA" || "$LAST_SHA" != "$CURRENT_SHA" ]]; then
echo "New commits detected — building."
echo "should_build=true" >> "$GITHUB_OUTPUT"
else
echo "No new commits since last successful build — skipping."
echo "should_build=false" >> "$GITHUB_OUTPUT"
fi
build-and-push-images:
needs: check-changes
if: needs.check-changes.outputs.should_build == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
@@ -67,10 +31,10 @@ jobs:
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Log in to the Container registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -78,7 +42,13 @@ jobs:
- name: Extract metadata for ${{ matrix.target }} image
id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
env:
# Annotate both the per-platform manifests and the multi-arch image
# index. The index level is what manifest-list consumers (Renovate's
# minimumReleaseAge soak check, provenance/SBOM tooling) read for the
# standard org.opencontainers.image.* annotations, including `created`.
DOCKER_METADATA_ANNOTATIONS_LEVELS: index,manifest
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}${{ matrix.image_name_suffix }}
tags: |
@@ -90,11 +60,11 @@ jobs:
type=ref,event=tag
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4.4.0
- name: Build and push ${{ matrix.target }} Docker image
id: push
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
platforms: linux/amd64,linux/arm64
context: .
@@ -105,10 +75,11 @@ jobs:
RELEASE_VERSION=${{ github.ref_name }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
annotations: ${{ steps.meta.outputs.annotations }}
- name: Generate artifact attestation for ${{ matrix.target }} image
if: github.event_name != 'pull_request'
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}${{ matrix.image_name_suffix }}
subject-digest: ${{ steps.push.outputs.digest }}
@@ -127,14 +98,14 @@ jobs:
LEGACY_NAME: calibre-web-automated-book-downloader
steps:
- name: Log in to registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4.4.0
- name: Create legacy aliases
run: |
+15 -15
View File
@@ -13,10 +13,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Install uv and Python
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
version: "0.11.3"
python-version: "3.14"
@@ -39,10 +39,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Install uv and Python
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
version: "0.11.3"
python-version: "3.14"
@@ -59,10 +59,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Install uv and Python
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
version: "0.11.3"
python-version: "3.14"
@@ -78,13 +78,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4.4.0
- name: Build shelfmark-lite image
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
context: .
target: shelfmark-lite
@@ -99,10 +99,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
cache: "npm"
@@ -122,10 +122,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
cache: "npm"
@@ -142,10 +142,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
cache: "npm"
+4 -4
View File
@@ -22,17 +22,17 @@ jobs:
language: [python, javascript-typescript]
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Initialize CodeQL
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v3
uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v3
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v3
uses: github/codeql-action/autobuild@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v3
uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v3
with:
category: "/language:${{ matrix.language }}"
+51
View File
@@ -0,0 +1,51 @@
name: Debounce dev image
# A burst of merges to main should publish one dev image, not one per commit.
# Each push to main waits out the 60-minute wait timer on the
# dev-image-debounce environment (Settings → Environments; waiting holds no
# runner), then dispatches the Docker workflow only if main still points at
# its commit. So only the last push of a burst builds, and the Docker
# workflow's history holds real builds only.
on:
push:
branches:
- main
permissions: {}
jobs:
debounce:
runs-on: ubuntu-latest
environment:
name: dev-image-debounce
deployment: false
permissions:
actions: write # dispatch the build, delete finished debounce runs
contents: read
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
steps:
- name: Dispatch the dev build if main is still at this commit
env:
CURRENT_SHA: ${{ github.sha }}
run: |
HEAD_SHA=$(gh api "repos/${GH_REPO}/git/ref/heads/main" --jq '.object.sha')
echo "This run's commit: ${CURRENT_SHA}"
echo "main HEAD now: ${HEAD_SHA}"
if [[ "$HEAD_SHA" == "$CURRENT_SHA" ]]; then
echo "No newer commits on main — dispatching the dev build."
gh workflow run build-and-publish-docker-image.yml --ref main
else
echo "main has moved on — the newer push's run will build it."
fi
# A finished debounce run is noise: it either did nothing or its build
# run is the record. Best effort, since runs from the same burst race to
# delete the same runs.
- name: Delete finished debounce runs
run: |
# On an HTTP error gh prints the error body to stdout, so bail out
# rather than loop over it.
ids=$(gh api "repos/${GH_REPO}/actions/workflows/dev-image-debounce.yml/runs?status=success&per_page=100" --jq '.workflow_runs[].id') || exit 0
for id in $ids; do
gh api -X DELETE "repos/${GH_REPO}/actions/runs/${id}" || true
done
+135
View File
@@ -0,0 +1,135 @@
name: E2E Platform
# Hermetic end-to-end matrix: boots the app under test against mock
# Anna's Archive / Cloudflare / bypasser / DNS / proxy / Tor / real torrent
# clients and runs the cluster suite under each config profile.
#
# On a PR that touches relevant code, this runs a fast core subset *and* the heavy
# `full` profile (real Chrome solving Cloudflare + DoH + real qBittorrent). The
# `e2e-required` job aggregates them into ONE status check — make that check a
# required status check in branch protection to block merges on any e2e failure
# (see tests/e2e/platform/README.md "Gating PRs").
on:
pull_request:
schedule:
- cron: "0 4 * * *" # nightly full matrix
workflow_dispatch:
concurrency:
group: e2e-platform-${{ github.ref }}
cancel-in-progress: true
jobs:
# Detect whether anything that affects the e2e platform changed. This lets the
# required check always report (never stuck "pending") while only spending CI on
# PRs that can actually break the e2e stack.
changes:
runs-on: ubuntu-latest
outputs:
relevant: ${{ steps.filter.outputs.relevant }}
steps:
- uses: actions/checkout@v7
- uses: dorny/paths-filter@v4.0.3
id: filter
with:
filters: |
relevant:
- 'shelfmark/**'
- 'entrypoint.sh'
- 'tor.sh'
- 'Dockerfile'
- 'tests/e2e/platform/**'
- '.github/workflows/e2e-platform.yml'
select-profiles:
needs: changes
if: needs.changes.outputs.relevant == 'true' || github.event_name != 'pull_request'
runs-on: ubuntu-latest
outputs:
profiles: ${{ steps.pick.outputs.profiles }}
steps:
- id: pick
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
echo 'profiles=["baseline","bypasser-external","dns-blocked"]' >> "$GITHUB_OUTPUT"
else
echo 'profiles=["baseline","bypasser-external","bypasser-disabled","dns-manual","dns-blocked","dns-doh","proxy-http","proxy-socks","tor","client-transmission","client-deluge","client-qbittorrent-delayed"]' >> "$GITHUB_OUTPUT"
fi
e2e:
needs: select-profiles
runs-on: ubuntu-latest
# A wedged app under test must not burn GitHub's 6h max job limit. A healthy
# profile run finishes in ~3-5 min; anything past 25 is hung, not slow.
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
profile: ${{ fromJSON(needs.select-profiles.outputs.profiles) }}
name: e2e (${{ matrix.profile }})
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Install uv and Python
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
python-version: "3.14"
enable-cache: true
- name: Sync dependencies
run: make install-python-dev
- name: Run e2e platform (${{ matrix.profile }})
run: tests/e2e/platform/run-e2e.sh env/${{ matrix.profile }}.env
- name: Dump shelfmark logs on failure
if: failure()
run: cat tests/e2e/platform/.state/shelfmark.${{ matrix.profile }}.log || true
# Heavy "everything real" job: real Chrome internal bypasser solving Cloudflare +
# DoH + real qBittorrent webseed download. Runs on relevant PRs and nightly.
e2e-full:
needs: changes
if: needs.changes.outputs.relevant == 'true' || github.event_name != 'pull_request'
runs-on: ubuntu-latest
# Real Chrome + qBittorrent is the slowest profile; still nowhere near 40 min.
timeout-minutes: 40
name: e2e (full — real Chrome + qBittorrent)
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Install uv and Python
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
python-version: "3.14"
enable-cache: true
- name: Sync dependencies
run: make install-python-dev
- name: Run full pipeline
run: tests/e2e/platform/run-e2e.sh env/full.env
- name: Dump logs on failure
if: failure()
run: |
cat tests/e2e/platform/.state/shelfmark.full.log || true
docker logs e2e-qbittorrent || true
# Single aggregated gate. ALWAYS runs (so a required check never hangs "pending"
# on unrelated PRs) and FAILS if any e2e job failed/was cancelled. Make THIS the
# required status check in branch protection.
e2e-required:
name: e2e required
needs: [e2e, e2e-full]
if: always()
runs-on: ubuntu-latest
steps:
- name: Aggregate e2e results
run: |
matrix='${{ needs.e2e.result }}'
full='${{ needs.e2e-full.result }}'
echo "e2e matrix=$matrix, e2e-full=$full"
# success or skipped (unrelated PR) is OK; failure/cancelled blocks.
for r in "$matrix" "$full"; do
if [ "$r" = "failure" ] || [ "$r" = "cancelled" ]; then
echo "::error::An e2e platform job did not pass — blocking."
exit 1
fi
done
echo "All e2e platform jobs passed (or were skipped as not relevant)."
+5
View File
@@ -166,6 +166,10 @@ ENV/
env.bak/
venv.bak/
# ...but the e2e platform test profiles live in an env/ dir and must be tracked
!tests/e2e/platform/env/
!tests/e2e/platform/env/*.env
# Spyder project settings
.spyderproject
.spyproject
@@ -232,6 +236,7 @@ pyrightconfig.json
*.local.*
AGENTS.md
.claude/
CLAUDE.md
.nvmrc
.playwright-mcp/
frontend-dist/
+70 -21
View File
@@ -4,7 +4,7 @@ ARG BUILDPLATFORM
ARG BUILDARCH
# Frontend build stage.
FROM --platform=$BUILDPLATFORM node:24-alpine@sha256:fb71d01345f11b708a3553c66e7c74074f2d506400ea81973343d915cb64eef0 AS frontend-builder
FROM --platform=$BUILDPLATFORM node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401c70e0e762c5b53302f905ec1c1 AS frontend-builder
# Helpful debug output to see what platforms BuildKit thinks it's using
RUN echo "BUILDPLATFORM=$BUILDPLATFORM BUILDARCH=$BUILDARCH TARGETPLATFORM=$TARGETPLATFORM TARGETARCH=$TARGETARCH"
@@ -24,10 +24,14 @@ COPY src/frontend/ ./
# Build the frontend
RUN npm run build
# Use python-slim as the base image
FROM python:3.14.5-slim@sha256:c845af9399020c7e562969a13689e929074a10fd057acd1b1fad06a2fb068e97 AS base
# uv is a build-time tool only, so it is mounted into the RUNs that need it rather
# than copied into the image. A COPY here would land ~24 MB in a `base` layer that
# every published image inherits, and a later `rm` cannot take it back out again --
# a RUN adds a layer, it does not rewrite the one underneath.
FROM ghcr.io/astral-sh/uv:0.12.16@sha256:adc68cd785ca65ea25c0611043b0a00b4ea3a22e1b54102fc084406d888082ee AS uv
COPY --from=ghcr.io/astral-sh/uv:0.11.3@sha256:90bbb3c16635e9627f49eec6539f956d70746c409209041800a0280b93152823 /uv /uvx /bin/
# Use python-slim as the base image
FROM python:3.14.7-slim@sha256:cad9a2c871761c413caa6fdd6441c783451e740a48aaeba60ae62a8b53525ef6 AS base
# Add build argument for version
ARG BUILD_VERSION
@@ -59,6 +63,11 @@ ENV FLASK_PORT=8084
# Configure locale, timezone, and perform initial cleanup in a single layer
RUN apt-get update && \
apt-get install -y --no-install-recommends \
# For building C-extensions (cffi, gevent, etc.)
gcc \
g++ \
libffi-dev \
python3-dev \
# For locale
locales tzdata \
# For healthcheck
@@ -72,7 +81,12 @@ RUN apt-get update && \
# --- Tor support (activated via USING_TOR=true) ---
tor \
supervisor \
iptables && \
iptables \
# --- WireGuard support (activated via USING_WIREGUARD=true) ---
wireguard-tools \
iproute2 \
procps \
ca-certificates && \
# Configure iptables alternatives for tor.sh compatibility
update-alternatives --set iptables /usr/sbin/iptables-legacy && \
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy && \
@@ -101,6 +115,7 @@ WORKDIR /app
# Install core Python dependencies first for better layer caching
COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=from=uv,source=/uv,target=/usr/local/bin/uv \
uv sync --locked --no-default-groups
# Runtime dependencies are installed into /app/.venv during the build. Remove the
@@ -131,15 +146,20 @@ RUN mkdir -p \
ln -s /tmp/shelfmark/seleniumbase/archived_files /app/archived_files && \
chown -R 1000:1000 /config /books /home/shelfmark /tmp/shelfmark /var/log/shelfmark && \
chmod -R a+rX /app && \
chmod +x /app/entrypoint.sh /app/tor.sh /app/genDebug.sh
chmod +x /app/entrypoint.sh /app/tor.sh /app/wireguard.sh /app/genDebug.sh
# Expose the application port
EXPOSE ${FLASK_PORT}
# Add healthcheck for container status
# Uses /api/health which doesn't require authentication
HEALTHCHECK --interval=60s --timeout=60s --start-period=60s --retries=3 \
CMD curl -s http://localhost:${FLASK_PORT}/api/health > /dev/null || exit 1
# Uses /api/health which doesn't require authentication.
# curl needs -f so an HTTP error status fails the probe instead of passing it:
# plain `curl -s` exits 0 on a 500, which reported a broken app as healthy.
# timeout stays well under interval so a hung probe cannot occupy a whole cycle.
# --start-interval matches the daemon default (5s), made explicit so startup
# probing does not depend on that default staying put.
HEALTHCHECK --interval=30s --timeout=10s --start-period=90s --start-interval=5s --retries=3 \
CMD curl -fsS http://localhost:${FLASK_PORT}/api/health > /dev/null || exit 1
# Use dumb-init as the entrypoint to handle signals properly
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
@@ -147,21 +167,39 @@ ENTRYPOINT ["/usr/bin/dumb-init", "--"]
FROM base AS shelfmark
RUN apt-get update && \
apt-get install -y --no-install-recommends \
# --- Chromium (PINNED to 149.0.7827.196) ---
# Debian's chromium 150.0.7871.46-1~deb13u1 security update (trixie-security,
# 2026-07-05) no longer opens the DevTools remote-debugging TCP port at all
# (no listener, no DevToolsActivePort file, even with a custom --user-data-dir;
# the RemoteDebuggingAllowed policy does not restore it). The SeleniumBase
# Pure-CDP driver connects through that port (/json/version), so with 150 every
# internal bypass dies with "Pure CDP browser startup failed" and all
# CF-gated downloads fail. Install the last working version from
# snapshot.debian.org until the bypasser can talk to Chromium >= 150 (e.g.
# pipe-based DevTools / UC mode) or seleniumbase ships a fix.
# Chrome 144+ requires --enable-unsafe-swiftshader for WebGL in Docker.
# This flag is set in internal_bypasser.py _get_browser_args()
ARG CHROMIUM_VERSION=149.0.7827.196-1~deb13u1
ARG CHROMIUM_SNAPSHOT=20260704T000000Z
RUN echo "deb [check-valid-until=no] https://snapshot.debian.org/archive/debian-security/${CHROMIUM_SNAPSHOT}/ trixie-security main" \
> /etc/apt/sources.list.d/chromium-pin-snapshot.list && \
apt-get update -o Acquire::Retries=5 && \
apt-get install -y --no-install-recommends -o Acquire::Retries=5 \
# For dumb display
xvfb \
# For screen recording
ffmpeg \
# --- Chromium (unpinned - uses latest from Debian repos) ---
# Chrome 144+ requires --enable-unsafe-swiftshader for WebGL in Docker.
# This flag is set in internal_bypasser.py _get_browser_args()
chromium \
chromium-common \
chromium=${CHROMIUM_VERSION} \
chromium-common=${CHROMIUM_VERSION} \
# For tkinter (pyautogui)
python3-tk \
# For RAR extraction
unrar-free && \
# Keep apt from "upgrading" chromium past the pin inside derived images
printf 'Package: chromium chromium-common\nPin: version %s\nPin-Priority: 1001\n' "${CHROMIUM_VERSION}" \
> /etc/apt/preferences.d/chromium-pin && \
rm /etc/apt/sources.list.d/chromium-pin-snapshot.list && \
# Create symlink so rarfile library can find unrar
ln -sf /usr/bin/unrar-free /usr/bin/unrar && \
# Cleanup APT cache
@@ -171,10 +209,24 @@ RUN apt-get update && \
# Install the browser automation stack used by the full image
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=from=uv,source=/uv,target=/usr/local/bin/uv \
uv sync --locked --no-default-groups --extra browser
# uv is only needed while building the image.
RUN rm -f /usr/bin/uv /usr/bin/uvx
# Deterministically resolve the Xlib namespace collision.
# pyautogui/mouseinfo pull the stale `python3-xlib` (0.15, 2014), while the
# `--extra browser` set pulls `python-xlib` (0.33). Both packages install into
# the same top-level `Xlib/` namespace, so whichever lands last wins. When the
# 2014 build wins, `Xlib.X` is missing `FamilyServerInterpreted`, which the
# SeleniumBase Pure-CDP driver requires at browser startup -> every bypass fails
# with "module 'Xlib.X' has no attribute 'FamilyServerInterpreted'" and no
# Cloudflare/DDoS-Guard protected download can complete. Drop the stale package
# and force python-xlib 0.33 to own the namespace. pyautogui runs fine against
# 0.33 (superset API).
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=from=uv,source=/uv,target=/usr/local/bin/uv \
uv pip uninstall --python /app/.venv/bin/python python3-xlib && \
uv pip install --python /app/.venv/bin/python --reinstall python-xlib==0.33 && \
/app/.venv/bin/python -c "import Xlib.X; assert hasattr(Xlib.X, 'FamilyServerInterpreted'), 'Xlib.X.FamilyServerInterpreted missing after fix'; print('Xlib namespace OK:', Xlib.__version__)"
# Keep SeleniumBase's bundled driver cache writable for the fixed non-root user.
RUN SELENIUMBASE_DRIVERS_DIR=$(/app/.venv/bin/python -c "import pathlib, seleniumbase; print(pathlib.Path(seleniumbase.__file__).resolve().parent / 'drivers')") && \
@@ -192,7 +244,4 @@ FROM base AS shelfmark-lite
ENV USING_EXTERNAL_BYPASSER=true
# uv is only needed while building the image.
RUN rm -f /usr/bin/uv /usr/bin/uvx
CMD ["/app/entrypoint.sh"]
+29 -1
View File
@@ -1,4 +1,4 @@
.PHONY: help install install-ci install-python-dev dev build preview frontend-typecheck frontend-lint frontend-format frontend-format-fix frontend-checks frontend-test clean up down docker-build refresh restart build-serve python-lint python-lint-fix python-format python-format-fix python-typecheck python-dead-code python-checks python-test python-test-cov checks fix
.PHONY: help install install-ci install-python-dev dev build preview frontend-typecheck frontend-lint frontend-format frontend-format-fix frontend-checks frontend-test clean up down docker-build refresh restart build-serve python-lint python-lint-fix python-format python-format-fix python-typecheck python-dead-code python-checks python-test python-test-cov e2e-platform e2e-platform-profile e2e-platform-matrix e2e-platform-full e2e-platform-build checks fix
# Frontend directory
FRONTEND_DIR := src/frontend
@@ -38,6 +38,10 @@ help:
@echo " python-checks - Run all Python static analysis checks"
@echo " python-test - Run unit tests"
@echo " python-test-cov - Run unit tests with coverage report"
@echo " e2e-platform - Run e2e docker platform (baseline profile)"
@echo " e2e-platform-profile PROFILE=<name> - Run e2e platform for one profile"
@echo " e2e-platform-matrix - Run e2e platform across all config profiles"
@echo " e2e-platform-full - Run heavy 'full' profile (real Chrome bypasser + DoH + real qBittorrent)"
@echo " clean - Remove node_modules and build artifacts"
@echo ""
@echo "Backend (Docker):"
@@ -127,6 +131,30 @@ python-test-cov:
@echo "Running tests with coverage..."
uv run pytest tests/ -x --tb=short -m "not integration and not e2e" --cov --cov-report=term-missing
# E2E docker platform: hermetic stack (mock AA/Cloudflare/bypasser/DNS/proxy/Tor)
# exercised across config profiles. See tests/e2e/platform/README.md.
E2E_PLATFORM_DIR := tests/e2e/platform
e2e-platform:
@echo "Running e2e platform (baseline profile)..."
cd $(E2E_PLATFORM_DIR) && ./run-e2e.sh env/baseline.env
e2e-platform-profile:
@echo "Running e2e platform (profile=$(PROFILE))..."
cd $(E2E_PLATFORM_DIR) && ./run-e2e.sh env/$(PROFILE).env
e2e-platform-matrix:
@echo "Running e2e platform matrix (all profiles)..."
cd $(E2E_PLATFORM_DIR) && ./run-matrix.sh
e2e-platform-build:
@echo "Pre-building e2e platform images once (reused across profiles)..."
cd $(E2E_PLATFORM_DIR) && ./build-images.sh
e2e-platform-full:
@echo "Running e2e platform FULL profile (real Chrome bypasser + DoH + real qBittorrent)..."
cd $(E2E_PLATFORM_DIR) && ./run-e2e.sh env/full.env
# Frontend linting
frontend-lint:
@echo "Running Oxlint..."
+43
View File
@@ -0,0 +1,43 @@
# Routes all traffic through a WireGuard tunnel - requires root startup.
#
# Mount your wg-quick config at /config/wg0.conf (read-only is fine). All
# non-LAN egress is forced through the tunnel by an iptables kill-switch, so if
# the tunnel drops, external traffic fails closed. LAN ranges (WebUI + internal
# download clients like Prowlarr / qBittorrent) stay reachable off-tunnel.
services:
shelfmark-wireguard:
image: ghcr.io/calibrain/shelfmark:latest
environment:
FLASK_PORT: 8084
# Quoted so it is passed as the literal string "true": entrypoint.sh compares
# $USING_WIREGUARD against "true", and some Compose implementations stringify
# a bare YAML boolean as "True", which would silently NOT enable WireGuard.
USING_WIREGUARD: "true"
# Path to the mounted wg-quick config (default shown).
WIREGUARD_CONFIG: /config/wg0.conf
# CIDRs kept OFF the tunnel so the WebUI and internal clients stay reachable.
LAN_NETWORK: 127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
PUID: 1000
PGID: 1000
cap_add:
- NET_ADMIN
- NET_RAW
# WireGuard needs the module/kernel routing; NET_ADMIN covers wg-quick.
sysctls:
- net.ipv4.conf.all.src_valid_mark=1
# Disable IPv6 in the container so the kill-switch can guarantee no IPv6
# leak path on kernels/containers without a usable ip6tables. wireguard.sh
# fails closed if IPv6 is neither kill-switched nor disabled. If your host
# DOES have a working ip6tables you may omit these (an ip6tables kill-switch
# is installed instead); or set WIREGUARD_ALLOW_IPV6_LEAK=true only if the
# container genuinely has no IPv6 connectivity.
- net.ipv6.conf.all.disable_ipv6=1
- net.ipv6.conf.default.disable_ipv6=1
ports:
- 8084:8084
restart: unless-stopped
volumes:
- /path/to/books:/books # Default destination for book downloads
- /path/to/config:/config # App configuration (put wg0.conf here)
# Required for torrent / usenet - path must match your download client's volume exactly
# - /path/to/downloads:/path/to/downloads
+60 -53
View File
@@ -1,72 +1,79 @@
[
{ "language": "English", "code": "en" },
{ "language": "Chinese", "code": "zh" },
{ "language": "Russian", "code": "ru" },
{ "language": "Spanish", "code": "es" },
{ "language": "French", "code": "fr" },
{ "language": "German", "code": "de" },
{ "language": "Italian", "code": "it" },
{ "language": "Portuguese", "code": "pt" },
{ "language": "Polish", "code": "pl" },
{ "language": "Bulgarian", "code": "bg" },
{ "language": "Dutch", "code": "nl" },
{ "language": "Japanese", "code": "ja" },
{ "language": "Arabic", "code": "ar" },
{ "language": "Hebrew", "code": "he" },
{ "language": "Turkish", "code": "tr" },
{ "language": "Hungarian", "code": "hu" },
{ "language": "Latin", "code": "la" },
{ "language": "Czech", "code": "cs" },
{ "language": "Korean", "code": "ko" },
{ "language": "Ukrainian", "code": "uk" },
{ "language": "Indonesian", "code": "id" },
{ "language": "Romanian", "code": "ro" },
{ "language": "Swedish", "code": "sv" },
{ "language": "Greek", "code": "el" },
{ "language": "Lithuanian", "code": "lt" },
{ "language": "Bangla", "code": "bn" },
{ "language": "Traditional Chinese", "code": "zh‑Hant" },
{ "language": "Afrikaans", "code": "af" },
{ "language": "Catalan", "code": "ca" },
{ "language": "Danish", "code": "da" },
{ "language": "Thai", "code": "th" },
{ "language": "Hindi", "code": "hi" },
{ "language": "Irish", "code": "ga" },
{ "language": "Latvian", "code": "lv" },
{ "language": "English", "code": "en", "aliases": ["eng"] },
{ "language": "Chinese", "code": "zh", "aliases": ["chi", "zho"] },
{ "language": "Russian", "code": "ru", "aliases": ["rus"] },
{ "language": "Spanish", "code": "es", "aliases": ["spa"] },
{ "language": "French", "code": "fr", "aliases": ["fra", "fre"] },
{ "language": "German", "code": "de", "aliases": ["deu", "ger"] },
{ "language": "Italian", "code": "it", "aliases": ["ita"] },
{ "language": "Portuguese", "code": "pt", "aliases": ["por"] },
{ "language": "Polish", "code": "pl", "aliases": ["pol"] },
{ "language": "Bulgarian", "code": "bg", "aliases": ["bul"] },
{ "language": "Dutch", "code": "nl", "aliases": ["dut", "nld"] },
{ "language": "Japanese", "code": "ja", "aliases": ["jap", "jpn"] },
{ "language": "Arabic", "code": "ar", "aliases": ["ara"] },
{ "language": "Hebrew", "code": "he", "aliases": ["heb"] },
{ "language": "Turkish", "code": "tr", "aliases": ["tur"] },
{ "language": "Hungarian", "code": "hu", "aliases": ["hun"] },
{ "language": "Latin", "code": "la", "aliases": ["lat"] },
{ "language": "Czech", "code": "cs", "aliases": ["ces", "cze"] },
{ "language": "Korean", "code": "ko", "aliases": ["kor"] },
{ "language": "Ukrainian", "code": "uk", "aliases": ["ukr"] },
{ "language": "Indonesian", "code": "id", "aliases": ["ind"] },
{ "language": "Romanian", "code": "ro", "aliases": ["rom", "ron"] },
{ "language": "Swedish", "code": "sv", "aliases": ["swe"] },
{ "language": "Greek", "code": "el", "aliases": ["ell", "gre"] },
{ "language": "Lithuanian", "code": "lt", "aliases": ["lit"] },
{ "language": "Bangla", "code": "bn", "aliases": ["ben", "bengali"] },
{ "language": "Traditional Chinese", "code": "zh-Hant", "aliases": ["zh‑Hant"] },
{ "language": "Afrikaans", "code": "af", "aliases": ["afr"] },
{ "language": "Catalan", "code": "ca", "aliases": ["cat"] },
{ "language": "Danish", "code": "da", "aliases": ["dan"] },
{ "language": "Thai", "code": "th", "aliases": ["tha"] },
{ "language": "Hindi", "code": "hi", "aliases": ["hin"] },
{ "language": "Irish", "code": "ga", "aliases": ["gle"] },
{ "language": "Latvian", "code": "lv", "aliases": ["lav"] },
{ "language": "Tibetan", "code": "bo" },
{ "language": "Kannada", "code": "kn" },
{ "language": "Serbian", "code": "sr" },
{ "language": "Persian", "code": "fa" },
{ "language": "Croatian", "code": "hr" },
{ "language": "Kannada", "code": "kn", "aliases": ["kan"] },
{ "language": "Serbian", "code": "sr", "aliases": ["srp"] },
{ "language": "Persian", "code": "fa", "aliases": ["farsi", "fas", "per"] },
{ "language": "Croatian", "code": "hr", "aliases": ["hrv"] },
{ "language": "Slovak", "code": "sk" },
{ "language": "Javanese", "code": "jv" },
{ "language": "Vietnamese", "code": "vi" },
{ "language": "Urdu", "code": "ur" },
{ "language": "Finnish", "code": "fi" },
{ "language": "Norwegian", "code": "no" },
{ "language": "Javanese", "code": "jv", "aliases": ["jav"] },
{ "language": "Vietnamese", "code": "vi", "aliases": ["vie"] },
{ "language": "Urdu", "code": "ur", "aliases": ["urd"] },
{ "language": "Finnish", "code": "fi", "aliases": ["fin"] },
{ "language": "Norwegian", "code": "no", "aliases": ["nor"] },
{ "language": "Kinyarwanda", "code": "rw" },
{ "language": "Tamil", "code": "ta" },
{ "language": "Tamil", "code": "ta", "aliases": ["tam"] },
{ "language": "Belarusian", "code": "be" },
{ "language": "Kazakh", "code": "kk" },
{ "language": "Mongolian", "code": "mn" },
{ "language": "Georgian", "code": "ka" },
{ "language": "Slovenian", "code": "sl" },
{ "language": "Slovenian", "code": "sl", "aliases": ["slv"] },
{ "language": "Esperanto", "code": "eo" },
{ "language": "Galician", "code": "gl" },
{ "language": "Marathi", "code": "mr" },
{ "language": "Filipino", "code": "fil" },
{ "language": "Gujarati", "code": "gu" },
{ "language": "Malayalam", "code": "ml" },
{ "language": "Marathi", "code": "mr", "aliases": ["mar"] },
{ "language": "Filipino", "code": "fil", "aliases": ["tagalog", "tgl"] },
{ "language": "Gujarati", "code": "gu", "aliases": ["guj"] },
{ "language": "Malayalam", "code": "ml", "aliases": ["mal"] },
{ "language": "Kyrgyz", "code": "ky" },
{ "language": "Azerbaijani", "code": "az" },
{ "language": "Quechua", "code": "qu" },
{ "language": "Swahili", "code": "sw" },
{ "language": "Bashkir", "code": "ba" },
{ "language": "Punjabi", "code": "pa" },
{ "language": "Malay", "code": "ms" },
{ "language": "Telugu", "code": "te" },
{ "language": "Punjabi", "code": "pa", "aliases": ["pan"] },
{ "language": "Malay", "code": "ms", "aliases": ["may", "msa"] },
{ "language": "Telugu", "code": "te", "aliases": ["tel"] },
{ "language": "Albanian", "code": "sq" },
{ "language": "Uyghur", "code": "ug" },
{ "language": "Armenian", "code": "hy" },
{ "language": "Shan", "code": "shn" }
{ "language": "Shan", "code": "shn" },
{ "language": "Bosnian", "code": "bs", "aliases": ["bos"] },
{ "language": "Burmese", "code": "my", "aliases": ["bur", "mya"] },
{ "language": "Estonian", "code": "et", "aliases": ["est"] },
{ "language": "Icelandic", "code": "is", "aliases": ["ice", "isl"] },
{ "language": "Manx", "code": "gv", "aliases": ["glv"] },
{ "language": "Scottish Gaelic", "code": "gd", "aliases": ["gla"] },
{ "language": "Sanskrit", "code": "sa", "aliases": ["san"] }
]
+25
View File
@@ -0,0 +1,25 @@
# Local development - WireGuard variant
services:
shelfmark-wireguard-dev:
extends:
file: ./compose/docker-compose.wireguard.yml
service: shelfmark-wireguard
build:
context: .
dockerfile: Dockerfile
target: shelfmark
environment:
# Quoted so they are passed as the literal string "true" (entrypoint.sh and
# the app compare against "true"); a bare YAML boolean can be stringified as
# "True" by some Compose variants, silently disabling the feature.
DEBUG: "true"
USING_WIREGUARD: "true"
WIREGUARD_CONFIG: /config/wg0.conf
volumes:
- ./.local/config:/config
- ./.local/books:/books
- ./.local/log:/var/log/shelfmark
- ./.local/tmp:/tmp/shelfmark
# Place your wg-quick config at ./.local/config/wg0.conf
# Required for torrent / usenet - path must match your download client's volume exactly
# - /path/to/downloads:/path/to/downloads
+72
View File
@@ -0,0 +1,72 @@
# API access with an API key
Shelfmark's web interface is driven entirely by a JSON API under `/api/`. Set
the `SHELFMARK_API_KEY` environment variable and scripts, dashboards and assistants can
call the same API without a browser session. Browser logins keep working
exactly as before: it is cookie **or** key.
## Set the key
```yaml
environment:
SHELFMARK_API_KEY: "a-long-random-secret"
```
Generate something long and random (for example `openssl rand -base64 32`).
A request carrying the key acts as an **admin**: the first admin user in
Shelfmark's user database. Create an admin before relying on the key in any
install that has none yet (for example an OIDC-only install). Without an
admin user, the key still authenticates as an admin identity with no user
row, and routes that need one (requests, activity) answer 403. To rotate,
change the variable and restart. Unset it and the feature is off. When the
instance runs with no authentication configured (`AUTH_METHOD=none`), the
key is simply unnecessary.
## Send the key
Either header works, and both are checked, so the key can be sent in
`X-Api-Key` behind a reverse proxy that sets its own `Authorization` header.
```bash
curl -s -H "Authorization: Bearer $SHELFMARK_API_KEY" https://shelfmark.example.com/api/downloads/active
curl -s -H "X-Api-Key: $SHELFMARK_API_KEY" https://shelfmark.example.com/api/downloads/active
```
A request that carries the key is authenticated by the key alone. Session
cookies are ignored and none are set. A bearer value that is not the configured
key is ignored and the request continues with normal session authentication,
so reverse proxies that forward their own tokens are unaffected; without a valid
session such a request gets the usual `401 {"error": "Unauthorized"}`. A
database error while resolving the admin returns
`500 {"error": "Authentication error"}` — never anonymous access.
`/api/auth/check` reflects the browser session only and ignores the key, so
use `/api/status` to verify a key.
## Examples
Search, then look up releases, then queue one (the same calls the web UI makes):
```bash
curl -s -H "Authorization: Bearer $SHELFMARK_API_KEY" \
"https://shelfmark.example.com/api/metadata/search?query=dune%20frank%20herbert"
# -> {"books":[{"provider":"hardcover","provider_id":"427363", ...}]}
curl -s -H "Authorization: Bearer $SHELFMARK_API_KEY" \
"https://shelfmark.example.com/api/releases?provider=hardcover&book_id=427363&content_type=ebook"
# -> {"releases":[{"source":"direct_download","source_id":"...", ...}], ...}
curl -s -X POST -H "Authorization: Bearer $SHELFMARK_API_KEY" -H "Content-Type: application/json" \
-d @release.json https://shelfmark.example.com/api/releases/download
# release.json = one object from "releases" (source and source_id are required)
curl -s -H "Authorization: Bearer $SHELFMARK_API_KEY" https://shelfmark.example.com/api/status
```
## Security notes
- The key is compared in constant time and is never logged.
- Keyed requests never set cookies and ignore any cookie sent with them.
- WebSocket (live activity) connections do not accept the key; poll `/api/status` instead.
- The key is a root-equivalent credential: an admin can configure a custom
post-download script that the server executes, so treat it like a root
password and send it only over HTTPS.
+4
View File
@@ -91,6 +91,10 @@ Example:
- Shelfmark can see the same files at `/downloads/books/...`
- Add a mapping from Remote Path `/data/torrents` to Local Path `/downloads`
If the files are copied or synced into Shelfmark on a delay, increase **Completed Path Wait (seconds)**
in Settings -> Advanced. The default is 60 seconds; seedbox or remote-sync setups may need a value
longer than the sync interval.
## File Processing Options
### Transfer Method (Torrent / Usenet Only)
+21
View File
@@ -276,6 +276,27 @@ class DownloadHandler(ABC):
pass
```
### Optional: Listing Files Before Download
Some releases bundle several books (a whole-series torrent). Shelfmark inspects a
release before queueing it so the user can review how it will be split into books.
Override `list_files` when your source can enumerate a release's files without
downloading it; the default returns `None`, which the UI reports as "can't inspect":
```python
from shelfmark.download.postprocess.packs import PackFile
def list_files(self, release_data: dict[str, Any]) -> list[PackFile] | None:
"""Return the release's files (release-relative paths + sizes), or None."""
torrent_bytes = ... # e.g. fetch the .torrent, or scrape the indexer's detail page
return extract_file_list_from_torrent(torrent_bytes) # from download.clients.torrent_utils
```
`release_data` is the same payload the frontend sends to `/api/releases/download`
(`source_id`, `download_url`, `content_type`, `series_name`, ...). Built-in examples:
Prowlarr parses the `.torrent` it already fetches (magnet-only releases return
`None`), and AudiobookBay reads the file table off its detail page.
### Download Method Parameters
| Parameter | Type | Description |
+388 -39
View File
@@ -7,6 +7,7 @@ This document lists all configuration options that can be set via environment va
## Table of Contents
- [Bootstrap Configuration](#bootstrap-configuration)
- [Egress / VPN Routing](#egress--vpn-routing)
- [General](#general)
- [Search Mode](#search-mode)
- [Downloads](#downloads)
@@ -22,6 +23,7 @@ This document lists all configuration options that can be set via environment va
- [Hardcover](#metadata-providers-hardcover)
- [Open Library](#metadata-providers-open-library)
- [Google Books](#metadata-providers-google-books)
- [Moly.hu](#metadata-providers-moly.hu)
- [Direct Download](#direct-download)
- [Download Sources](#direct-download-download-sources)
- [Cloudflare Bypass](#direct-download-cloudflare-bypass)
@@ -45,6 +47,7 @@ These environment variables are used at startup before the settings system loads
| `CWA_DB_PATH` | Path to the Calibre-Web database for authentication integration. | string (path) | `/auth/app.db` |
| `HIDE_LOCAL_AUTH` | Hide the username/password login form when OIDC is active. | boolean | `false` |
| `DISABLE_LOCAL_AUTH` | Disable username/password login and remove the local-admin prerequisite for OIDC. Implies HIDE_LOCAL_AUTH; with AUTH_METHOD=builtin, everyone is locked out until auth env vars are changed. | boolean | `false` |
| `SHELFMARK_API_KEY` | Optional static API key. When set, requests carrying it as 'Authorization: Bearer <key>' (or X-Api-Key) are authenticated as an admin; browser sessions keep working. Unset = off. | string | `unset` |
| `OIDC_AUTO_REDIRECT` | Automatically redirect to the OIDC provider instead of showing the login page. | boolean | `false` |
| `DOCKERMODE` | Indicates the application is running inside a Docker container. | boolean | `false` |
| `ONBOARDING` | Show the onboarding wizard on first run. Set to false to skip (useful for ephemeral storage). | boolean | `true` |
@@ -122,6 +125,13 @@ Disable username/password login and remove the local-admin prerequisite for OIDC
- **Type:** boolean
- **Default:** `false`
#### `SHELFMARK_API_KEY`
Optional static API key. When set, requests carrying it as 'Authorization: Bearer <key>' (or X-Api-Key) are authenticated as an admin; browser sessions keep working. Unset = off.
- **Type:** string
- **Default:** `unset`
#### `OIDC_AUTO_REDIRECT`
Automatically redirect to the OIDC provider instead of showing the login page.
@@ -145,6 +155,98 @@ Show the onboarding wizard on first run. Set to false to skip (useful for epheme
</details>
## Egress / VPN Routing
These startup-only variables are consumed by `entrypoint.sh` / `wireguard.sh` to select and configure the WireGuard transparent-egress kill-switch. `USING_WIREGUARD` and [`USING_TOR`](#using_tor) (documented under Network) are mutually exclusive; both require root startup.
| Variable | Description | Type | Default |
|----------|-------------|------|---------|
| `USING_WIREGUARD` | Route all traffic through a WireGuard VPN tunnel with a fail-closed iptables kill-switch (non-tunnel egress is dropped). Requires root startup and NET_ADMIN (plus NET_RAW). Mutually exclusive with USING_TOR. | boolean | `false` |
| `WIREGUARD_CONFIG` | Path to the mounted wg-quick configuration file. | string (path) | `/config/wg0.conf` |
| `WIREGUARD_INTERFACE` | WireGuard interface name brought up by wg-quick. | string | `wg0` |
| `LAN_NETWORK` | Comma-separated CIDRs kept off the tunnel so the WebUI and internal download clients (Prowlarr, qBittorrent) stay reachable. | string (comma-separated) | `127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16` |
| `WIREGUARD_ENFORCE_DNS` | Pin the container's resolver so DNS cannot silently fall back to an off-tunnel path. The resolver used is WIREGUARD_DNS if set, else the tunnel config's DNS = line. This does NOT force queries through the tunnel: it is designed for a trusted LAN resolver kept reachable off-tunnel via LAN_NETWORK (the query leaves over the LAN; the resolver encrypts upstream while the download still egresses via the tunnel). Special case: when Docker's embedded resolver (nameserver 127.0.0.11) is present, it is PRESERVED so container-name resolution (e.g. prowlarr, qbittorrent) keeps working, and the embedded resolver's upstream must be pinned via the container's compose dns: list. Fails closed (refuses to start) only when no embedded resolver is present AND no resolver is defined, or /etc/resolv.conf is not writable. | boolean | `true` |
| `WIREGUARD_DNS` | Explicit resolver(s) (comma/space separated) to pin when WIREGUARD_ENFORCE_DNS is true and Docker's embedded resolver is NOT in use. Use when the VPN's pushed DNS filters domains you need; point it at a resolver reachable via the tunnel or an allowed LAN resolver. NOTE: when the embedded resolver (127.0.0.11) is present it is preserved and this value cannot repoint its upstream from inside the container — set the container's compose dns: list to the trusted resolver instead. | string (comma-separated) | `unset (uses config DNS = line)` |
| `WIREGUARD_DISABLE_IPV6` | Strip IPv6 Address/AllowedIPs/DNS from the tunnel config before wg-quick (many container kernels lack the ip6tables raw table wg-quick needs) and remove IPv6 as a leak surface. | boolean | `true` |
| `WIREGUARD_ALLOW_IPV6_LEAK` | Escape hatch: continue startup even when an IPv6 kill-switch cannot be installed AND IPv6 cannot be disabled. Only set when the container has no IPv6 connectivity, as IPv6 egress may otherwise bypass the tunnel. | boolean | `false` |
| `WIREGUARD_ALLOW_WEBUI_OFFTUNNEL` | When false (default) the kill-switch is strictly fail-closed: the only off-tunnel egress permitted is loopback, the tunnel device and the LAN allowlist. Set true only if a NON-LAN client (e.g. a public reverse proxy on a different segment) must reach the WebUI; it permits app-server REPLY packets (--sport FLASK_PORT, conntrack REPLY) to leave off-tunnel. Server replies only, never client-initiated egress, so it cannot leak outbound browsing/downloads or the real IP for outbound requests, but it is still an off-tunnel path while the tunnel is down, hence opt-in. LAN WebUI clients never need it (covered by LAN_NETWORK). | boolean | `false` |
| `WIREGUARD_STALE_AFTER` | Seconds since the last WireGuard handshake before the healthcheck bounces the tunnel. | number | `180` |
<details>
<summary>Detailed descriptions</summary>
#### `USING_WIREGUARD`
Route all traffic through a WireGuard VPN tunnel with a fail-closed iptables kill-switch (non-tunnel egress is dropped). Requires root startup and NET_ADMIN (plus NET_RAW). Mutually exclusive with USING_TOR.
- **Type:** boolean
- **Default:** `false`
#### `WIREGUARD_CONFIG`
Path to the mounted wg-quick configuration file.
- **Type:** string (path)
- **Default:** `/config/wg0.conf`
#### `WIREGUARD_INTERFACE`
WireGuard interface name brought up by wg-quick.
- **Type:** string
- **Default:** `wg0`
#### `LAN_NETWORK`
Comma-separated CIDRs kept off the tunnel so the WebUI and internal download clients (Prowlarr, qBittorrent) stay reachable.
- **Type:** string (comma-separated)
- **Default:** `127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16`
#### `WIREGUARD_ENFORCE_DNS`
Pin the container's resolver so DNS cannot silently fall back to an off-tunnel path. The resolver used is WIREGUARD_DNS if set, else the tunnel config's DNS = line. This does NOT force queries through the tunnel: it is designed for a trusted LAN resolver kept reachable off-tunnel via LAN_NETWORK (the query leaves over the LAN; the resolver encrypts upstream while the download still egresses via the tunnel). Special case: when Docker's embedded resolver (nameserver 127.0.0.11) is present, it is PRESERVED so container-name resolution (e.g. prowlarr, qbittorrent) keeps working, and the embedded resolver's upstream must be pinned via the container's compose dns: list. Fails closed (refuses to start) only when no embedded resolver is present AND no resolver is defined, or /etc/resolv.conf is not writable.
- **Type:** boolean
- **Default:** `true`
#### `WIREGUARD_DNS`
Explicit resolver(s) (comma/space separated) to pin when WIREGUARD_ENFORCE_DNS is true and Docker's embedded resolver is NOT in use. Use when the VPN's pushed DNS filters domains you need; point it at a resolver reachable via the tunnel or an allowed LAN resolver. NOTE: when the embedded resolver (127.0.0.11) is present it is preserved and this value cannot repoint its upstream from inside the container — set the container's compose dns: list to the trusted resolver instead.
- **Type:** string (comma-separated)
- **Default:** `unset (uses config DNS = line)`
#### `WIREGUARD_DISABLE_IPV6`
Strip IPv6 Address/AllowedIPs/DNS from the tunnel config before wg-quick (many container kernels lack the ip6tables raw table wg-quick needs) and remove IPv6 as a leak surface.
- **Type:** boolean
- **Default:** `true`
#### `WIREGUARD_ALLOW_IPV6_LEAK`
Escape hatch: continue startup even when an IPv6 kill-switch cannot be installed AND IPv6 cannot be disabled. Only set when the container has no IPv6 connectivity, as IPv6 egress may otherwise bypass the tunnel.
- **Type:** boolean
- **Default:** `false`
#### `WIREGUARD_ALLOW_WEBUI_OFFTUNNEL`
When false (default) the kill-switch is strictly fail-closed: the only off-tunnel egress permitted is loopback, the tunnel device and the LAN allowlist. Set true only if a NON-LAN client (e.g. a public reverse proxy on a different segment) must reach the WebUI; it permits app-server REPLY packets (--sport FLASK_PORT, conntrack REPLY) to leave off-tunnel. Server replies only, never client-initiated egress, so it cannot leak outbound browsing/downloads or the real IP for outbound requests, but it is still an off-tunnel path while the tunnel is down, hence opt-in. LAN WebUI clients never need it (covered by LAN_NETWORK).
- **Type:** boolean
- **Default:** `false`
#### `WIREGUARD_STALE_AFTER`
Seconds since the last WireGuard handshake before the healthcheck bounces the tunnel.
- **Type:** number
- **Default:** `180`
</details>
## General
| Variable | Description | Type | Default |
@@ -153,7 +255,7 @@ Show the onboarding wizard on first run. Set to false to skip (useful for epheme
| `CALIBRE_WEB_URL` | Adds a navigation button to your book library (Calibre-Web Automated, Grimmory, etc). | string | _none_ |
| `AUDIOBOOK_LIBRARY_URL` | Adds a separate navigation button for your audiobook library (Audiobookshelf, Plex, etc). When both URLs are set, icons are shown instead of text. | string | _none_ |
| `SUPPORTED_FORMATS` | Book formats to include in search results. ZIP/RAR archives are extracted automatically and book files are used if found. | string (comma-separated) | `epub,mobi,azw3,fb2,djvu,cbz,cbr` |
| `SUPPORTED_AUDIOBOOK_FORMATS` | Audiobook formats to include in search results. ZIP/RAR archives are extracted automatically and audiobook files are used if found. | string (comma-separated) | `m4b,mp3` |
| `SUPPORTED_AUDIOBOOK_FORMATS` | Audiobook formats to include in search results. ZIP/RAR archives are extracted automatically and audiobook files are used if found. | string (comma-separated) | `m4b,mp3,m4a,mp4,flac,ogg,wma,aac,wav,opus,zip,rar` |
| `BOOK_LANGUAGE` | Default language filter for searches. | string (comma-separated) | `en` |
<details>
@@ -202,16 +304,7 @@ Book formats to include in search results. ZIP/RAR archives are extracted automa
Audiobook formats to include in search results. ZIP/RAR archives are extracted automatically and audiobook files are used if found.
- **Type:** string (comma-separated)
- **Default:** `m4b,mp3`
#### `BOOK_LANGUAGE`
**Default Book Languages**
Default language filter for searches.
- **Type:** string (comma-separated)
- **Default:** `en`
- **Default:** `m4b,mp3,m4a,mp4,flac,ogg,wma,aac,wav,opus,zip,rar`
</details>
@@ -220,6 +313,7 @@ Default language filter for searches.
| Variable | Description | Type | Default |
|----------|-------------|------|---------|
| `SEARCH_MODE` | How you want to search for and download books. | string (choice) | `universal` |
| `BOOK_LANGUAGE` | Default language filter for searches. Users can override this for their own account. | string (comma-separated) | `en` |
| `AA_DEFAULT_SORT` | Default sort order for search results. | string (choice) | `relevance` |
| `SHOW_RELEASE_SOURCE_LINKS` | Show clickable release-source links in release and details modals. Metadata provider links stay enabled. | boolean | `true` |
| `SHOW_COMBINED_SELECTOR` | Show the option to search for and download both a book and audiobook together. | boolean | `true` |
@@ -243,6 +337,15 @@ How you want to search for and download books.
- **Default:** `universal`
- **Options:** `direct` (Direct), `universal` (Universal)
#### `BOOK_LANGUAGE`
**Default Book Languages**
Default language filter for searches. Users can override this for their own account.
- **Type:** string (comma-separated)
- **Default:** `en`
#### `AA_DEFAULT_SORT`
**Default Sort Order**
@@ -339,8 +442,9 @@ The release source tab to open by default in the release modal for audiobooks. U
| `BOOKS_OUTPUT_MODE` | Choose where completed book files are sent. | string (choice) | `folder` |
| `INGEST_DIR` | Directory where downloaded files are saved. Use {User} for per-user folders (e.g. /books/{User}). | string | `/books` |
| `FILE_ORGANIZATION` | Choose how downloaded book files are named and organized. | string (choice) | `rename` |
| `TEMPLATE_RENAME` | Variables: {Author}, {Title}, {Year}, {User}, {OriginalName} (source filename without extension). Universal adds: {Series}, {SeriesPosition}, {Subtitle}, {PrimaryTitle}. Use arbitrary prefix/suffix: {Vol. SeriesPosition - } outputs 'Vol. 2 - ' when set, nothing when empty. Rename templates are filename-only (no '/' or '\'); use Organize for folders. Applies to single-file downloads. | string | `{Author} - {Title} ({Year})` |
| `TEMPLATE_ORGANIZE` | Use / to create folders. Variables: {Author}, {Title}, {Year}, {User}, {OriginalName} (source filename without extension). Universal adds: {Series}, {SeriesPosition}, {Subtitle}, {PrimaryTitle}. Use arbitrary prefix/suffix: {Vol. SeriesPosition - } outputs 'Vol. 2 - ' when set, nothing when empty. | string | `{Author}/{Title} ({Year})` |
| `NAMING_WORD_SEPARATOR` | Replaces spaces inside naming template values (e.g. 'Conan Doyle' -> 'Conan.Doyle' with '.'). Applies to books and audiobooks, rename and organize templates alike. Literal characters typed into a template (like the '-' in '{Author} - {Title}') are left as-is. Leave empty to keep spaces as-is. | string | _empty string_ |
| `TEMPLATE_RENAME` | Variables: {Author}, {FirstAuthor} (first of several authors), {Title}, {Year}, {Language}, {User}, {OriginalName} (source filename without extension). Universal adds: {Series}, {SeriesPosition}, {Subtitle}, {PrimaryTitle}. Use arbitrary prefix/suffix: {Vol. SeriesPosition - } outputs 'Vol. 2 - ' when set, nothing when empty. Rename templates are filename-only (no '/' or '\'); use Organize for folders. Applies to single-file downloads. | string | `{Author} - {Title} ({Year})` |
| `TEMPLATE_ORGANIZE` | Use / to create folders. Variables: {Author}, {FirstAuthor} (first of several authors), {Title}, {Year}, {Language}, {User}, {OriginalName} (source filename without extension). Universal adds: {Series}, {SeriesPosition}, {Subtitle}, {PrimaryTitle}. Use arbitrary prefix/suffix: {Vol. SeriesPosition - } outputs 'Vol. 2 - ' when set, nothing when empty. | string | `{Author}/{Title} ({Year})` |
| `HARDLINK_TORRENTS` | Create hardlinks instead of copying. Preserves seeding but archives won't be extracted. Don't use if destination is a library ingest folder. | boolean | `false` |
| `BOOKLORE_HOST` | Base URL of your Grimmory instance | string | _none_ |
| `BOOKLORE_USERNAME` | Grimmory account username | string | _none_ |
@@ -361,8 +465,8 @@ The release source tab to open by default in the release modal for audiobooks. U
| `EMAIL_ALLOW_UNVERIFIED_TLS` | Disable TLS certificate verification (not recommended). | boolean | `false` |
| `DESTINATION_AUDIOBOOK` | Directory where downloaded audiobook files are saved. Leave empty to use the Books destination. | string | _none_ |
| `FILE_ORGANIZATION_AUDIOBOOK` | Choose how downloaded audiobook files are named and organized. | string (choice) | `rename` |
| `TEMPLATE_AUDIOBOOK_RENAME` | Variables: {Author}, {Title}, {Year}, {User}, {OriginalName} (source filename without extension), {Series}, {SeriesPosition}, {Subtitle}, {PrimaryTitle}, {PartNumber}. Use arbitrary prefix/suffix: {Vol. SeriesPosition - } outputs 'Vol. 2 - ' when set, nothing when empty. Rename templates are filename-only (no '/' or '\'); use Organize for folders. Applies to single-file downloads. | string | `{Author} - {Title}` |
| `TEMPLATE_AUDIOBOOK_ORGANIZE` | Use / to create folders. Variables: {Author}, {Title}, {Year}, {User}, {OriginalName} (source filename without extension), {Series}, {SeriesPosition}, {Subtitle}, {PrimaryTitle}, {PartNumber}. Use arbitrary prefix/suffix: {Vol. SeriesPosition - } outputs 'Vol. 2 - ' when set, nothing when empty. | string | `{Author}/{Title}/{Title}` |
| `TEMPLATE_AUDIOBOOK_RENAME` | Variables: {Author}, {FirstAuthor} (first of several authors), {Title}, {Year}, {Language}, {User}, {OriginalName} (source filename without extension), {Series}, {SeriesPosition}, {Subtitle}, {PrimaryTitle}, {PartNumber}. Use arbitrary prefix/suffix: {Vol. SeriesPosition - } outputs 'Vol. 2 - ' when set, nothing when empty. Rename templates are filename-only (no '/' or '\'); use Organize for folders. Applies to single-file downloads. | string | `{Author} - {Title}` |
| `TEMPLATE_AUDIOBOOK_ORGANIZE` | Use / to create folders. Variables: {Author}, {FirstAuthor} (first of several authors), {Title}, {Year}, {Language}, {User}, {OriginalName} (source filename without extension), {Series}, {SeriesPosition}, {Subtitle}, {PrimaryTitle}, {PartNumber}. Use arbitrary prefix/suffix: {Vol. SeriesPosition - } outputs 'Vol. 2 - ' when set, nothing when empty. | string | `{Author}/{Title}/{Title}` |
| `HARDLINK_TORRENTS_AUDIOBOOK` | Create hardlinks instead of copying. Preserves seeding but archives won't be extracted. Don't use if destination is a library ingest folder. | boolean | `true` |
| `AUTO_OPEN_DOWNLOADS_SIDEBAR` | Automatically open the downloads sidebar when a new download is queued. | boolean | `false` |
| `DOWNLOAD_TO_BROWSER_CONTENT_TYPES` | Automatically download completed files to your browser for the selected content types. | string (comma-separated) | _empty list_ |
@@ -402,11 +506,20 @@ Choose how downloaded book files are named and organized.
- **Default:** `rename`
- **Options:** `none` (None), `rename` (Rename Only), `organize` (Rename and Organize)
#### `NAMING_WORD_SEPARATOR`
**Word Separator**
Replaces spaces inside naming template values (e.g. 'Conan Doyle' -> 'Conan.Doyle' with '.'). Applies to books and audiobooks, rename and organize templates alike. Literal characters typed into a template (like the '-' in '{Author} - {Title}') are left as-is. Leave empty to keep spaces as-is.
- **Type:** string
- **Default:** _empty string_
#### `TEMPLATE_RENAME`
**Naming Template**
Variables: {Author}, {Title}, {Year}, {User}, {OriginalName} (source filename without extension). Universal adds: {Series}, {SeriesPosition}, {Subtitle}, {PrimaryTitle}. Use arbitrary prefix/suffix: {Vol. SeriesPosition - } outputs 'Vol. 2 - ' when set, nothing when empty. Rename templates are filename-only (no '/' or '\'); use Organize for folders. Applies to single-file downloads.
Variables: {Author}, {FirstAuthor} (first of several authors), {Title}, {Year}, {Language}, {User}, {OriginalName} (source filename without extension). Universal adds: {Series}, {SeriesPosition}, {Subtitle}, {PrimaryTitle}. Use arbitrary prefix/suffix: {Vol. SeriesPosition - } outputs 'Vol. 2 - ' when set, nothing when empty. Rename templates are filename-only (no '/' or '\'); use Organize for folders. Applies to single-file downloads.
- **Type:** string
- **Default:** `{Author} - {Title} ({Year})`
@@ -415,7 +528,7 @@ Variables: {Author}, {Title}, {Year}, {User}, {OriginalName} (source filename wi
**Path Template**
Use / to create folders. Variables: {Author}, {Title}, {Year}, {User}, {OriginalName} (source filename without extension). Universal adds: {Series}, {SeriesPosition}, {Subtitle}, {PrimaryTitle}. Use arbitrary prefix/suffix: {Vol. SeriesPosition - } outputs 'Vol. 2 - ' when set, nothing when empty.
Use / to create folders. Variables: {Author}, {FirstAuthor} (first of several authors), {Title}, {Year}, {Language}, {User}, {OriginalName} (source filename without extension). Universal adds: {Series}, {SeriesPosition}, {Subtitle}, {PrimaryTitle}. Use arbitrary prefix/suffix: {Vol. SeriesPosition - } outputs 'Vol. 2 - ' when set, nothing when empty.
- **Type:** string
- **Default:** `{Author}/{Title} ({Year})`
@@ -610,13 +723,13 @@ Choose how downloaded audiobook files are named and organized.
- **Type:** string (choice)
- **Default:** `rename`
- **Options:** `none` (None), `rename` (Rename Only), `organize` (Rename and Organize)
- **Options:** `none` (None), `rename` (Rename Only), `organize` (Rename and Organize), `rename_and_group` (Rename and Group)
#### `TEMPLATE_AUDIOBOOK_RENAME`
**Naming Template**
Variables: {Author}, {Title}, {Year}, {User}, {OriginalName} (source filename without extension), {Series}, {SeriesPosition}, {Subtitle}, {PrimaryTitle}, {PartNumber}. Use arbitrary prefix/suffix: {Vol. SeriesPosition - } outputs 'Vol. 2 - ' when set, nothing when empty. Rename templates are filename-only (no '/' or '\'); use Organize for folders. Applies to single-file downloads.
Variables: {Author}, {FirstAuthor} (first of several authors), {Title}, {Year}, {Language}, {User}, {OriginalName} (source filename without extension), {Series}, {SeriesPosition}, {Subtitle}, {PrimaryTitle}, {PartNumber}. Use arbitrary prefix/suffix: {Vol. SeriesPosition - } outputs 'Vol. 2 - ' when set, nothing when empty. Rename templates are filename-only (no '/' or '\'); use Organize for folders. Applies to single-file downloads.
- **Type:** string
- **Default:** `{Author} - {Title}`
@@ -625,7 +738,7 @@ Variables: {Author}, {Title}, {Year}, {User}, {OriginalName} (source filename wi
**Path Template**
Use / to create folders. Variables: {Author}, {Title}, {Year}, {User}, {OriginalName} (source filename without extension), {Series}, {SeriesPosition}, {Subtitle}, {PrimaryTitle}, {PartNumber}. Use arbitrary prefix/suffix: {Vol. SeriesPosition - } outputs 'Vol. 2 - ' when set, nothing when empty.
Use / to create folders. Variables: {Author}, {FirstAuthor} (first of several authors), {Title}, {Year}, {Language}, {User}, {OriginalName} (source filename without extension), {Series}, {SeriesPosition}, {Subtitle}, {PrimaryTitle}, {PartNumber}. Use arbitrary prefix/suffix: {Vol. SeriesPosition - } outputs 'Vol. 2 - ' when set, nothing when empty.
- **Type:** string
- **Default:** `{Author}/{Title}/{Title}`
@@ -655,6 +768,7 @@ Automatically open the downloads sidebar when a new download is queued.
Automatically download completed files to your browser for the selected content types.
- **Type:** string (comma-separated)
- **Default:** _empty list_
#### `MAX_CONCURRENT_DOWNLOADS`
@@ -688,6 +802,7 @@ How long to keep completed/failed downloads in the queue display.
| `PROXY_AUTH_USER_HEADER` | The HTTP header your proxy uses to pass the authenticated username. | string | `X-Auth-User` |
| `PROXY_AUTH_LOGOUT_URL` | The URL to redirect users to for logging out. Leave empty to disable logout functionality. | string | _empty string_ |
| `PROXY_AUTH_ADMIN_GROUP_HEADER` | Optional: header your proxy uses to pass user groups/roles. | string | `X-Auth-Groups` |
| `PROXY_AUTH_DEFAULT_ROLE` | Role for users the proxy authenticates for the first time when no admin group is configured. The first account is always an admin so the instance is never left without one. | string (choice) | `user` |
| `PROXY_AUTH_ADMIN_GROUP_NAME` | Optional: users in this group are treated as admins. Leave blank to skip group-based admin detection. | string | _empty string_ |
| `OIDC_DISCOVERY_URL` | OpenID Connect discovery endpoint URL. Usually ends with /.well-known/openid-configuration. | string | _none_ |
| `OIDC_CLIENT_ID` | OAuth2 client ID from your identity provider. | string | _none_ |
@@ -747,6 +862,16 @@ Optional: users in this group are treated as admins. Leave blank to skip group-b
- **Type:** string
- **Default:** _empty string_
#### `PROXY_AUTH_DEFAULT_ROLE`
**Proxy Auth Default Role**
Role for users the proxy authenticates for the first time when no admin group is configured. The first account is always an admin so the instance is never left without one.
- **Type:** string (choice)
- **Default:** `user`
- **Options:** `user` (User), `admin` (Admin)
#### `OIDC_DISCOVERY_URL`
@@ -953,11 +1078,13 @@ Comma-separated hosts to bypass proxy (e.g., localhost,127.0.0.1,10.*,*.local)
|----------|-------------|------|---------|
| `URL_BASE` | Optional URL path prefix. Use a path like /shelfmark (no hostname). Leave blank for root. | string | _none_ |
| `DEBUG` | Enable verbose logging to console and file. Not recommended for normal use. | boolean | `false` |
| `LOG_LEVEL` | Lowest severity written to the console and log file. Ignored while Debug Mode is on, which forces Debug. | string (choice) | `INFO` |
| `MAIN_LOOP_SLEEP_TIME` | How often the download queue is checked for new items. | number | `5` |
| `DOWNLOAD_PROGRESS_UPDATE_INTERVAL` | How often download progress is broadcast to the UI. | number | `1` |
| `CUSTOM_SCRIPT` | Path to a script to run after each successful download. Must be executable. | string | _none_ |
| `CUSTOM_SCRIPT_PATH_MODE` | Pass the path to the custom script as an absolute path or relative to the destination folder. | string (choice) | `absolute` |
| `CUSTOM_SCRIPT_JSON_PAYLOAD` | Send a JSON payload to the script via stdin. Useful for multi-file imports (audiobooks) or richer metadata without relying on path parsing. | boolean | `false` |
| `DOWNLOAD_CLIENT_COMPLETED_PATH_TIMEOUT` | How long to wait after a torrent or usenet client reports completion for the completed file path to become visible to Shelfmark. Increase this for seedbox or remote-sync workflows. | number | `60` |
| `COVERS_CACHE_ENABLED` | Cache book covers on the server for faster loading. | boolean | `true` |
| `COVERS_CACHE_TTL` | How long to keep cached covers. Set to 0 to keep forever (recommended for static artwork). | number | `0` |
| `COVERS_CACHE_MAX_SIZE_MB` | Maximum disk space for cached covers. Oldest images are removed when limit is reached. | number | `500` |
@@ -988,6 +1115,17 @@ Enable verbose logging to console and file. Not recommended for normal use.
- **Default:** `false`
- **Requires restart:** Yes
#### `LOG_LEVEL`
**Log Level**
Lowest severity written to the console and log file. Ignored while Debug Mode is on, which forces Debug.
- **Type:** string (choice)
- **Default:** `INFO`
- **Requires restart:** Yes
- **Options:** `DEBUG` (Debug), `INFO` (Info), `WARNING` (Warning), `ERROR` (Error), `CRITICAL` (Critical)
#### `MAIN_LOOP_SLEEP_TIME`
**Queue Check Interval (seconds)**
@@ -1038,6 +1176,16 @@ Send a JSON payload to the script via stdin. Useful for multi-file imports (audi
- **Type:** boolean
- **Default:** `false`
#### `DOWNLOAD_CLIENT_COMPLETED_PATH_TIMEOUT`
**Completed Path Wait (seconds)**
How long to wait after a torrent or usenet client reports completion for the completed file path to become visible to Shelfmark. Increase this for seedbox or remote-sync workflows.
- **Type:** number
- **Default:** `60`
- **Constraints:** min: 0, max: 3600
#### `COVERS_CACHE_ENABLED`
**Enable Cover Cache**
@@ -1106,7 +1254,9 @@ How long to cache individual book details. Default: 600 (10 minutes). Max: 60480
| `PROWLARR_URL` | Base URL of your Prowlarr instance | string | _none_ |
| `PROWLARR_API_KEY` | Found in Prowlarr: Settings > General > API Key | string (secret) | _none_ |
| `PROWLARR_INDEXERS` | Select which indexers to search. 📚 = has book categories. Leave empty to search all. | string (comma-separated) | _empty list_ |
| `PROWLARR_INDEXER_TIMEOUT` | How long to wait for a single indexer to answer a search. Indexers behind FlareSolverr can need 90 seconds or more while a cold Cloudflare challenge is solved; raise this if searches come back empty and the Prowlarr log shows the search still running. | number | `90` |
| `PROWLARR_AUTO_EXPAND` | Automatically retry search without category filtering if no results are found | boolean | `false` |
| `PROWLARR_COLLAPSE_DUPLICATES` | Collapse a release that several indexer entries returned down to a single row, keeping the entry with the best Prowlarr priority. Turn this off to see every entry that carried it, which is what makes results from filter-specific entries (freeleech and the like) visible. | boolean | `true` |
| `PROWLARR_USE_SEED_PREFERENCES` | Apply per-indexer seed time and ratio preferences from Prowlarr when sending torrents to the download client | boolean | `false` |
<details>
@@ -1150,6 +1300,16 @@ Select which indexers to search. 📚 = has book categories. Leave empty to sear
- **Type:** string (comma-separated)
- **Default:** _empty list_
#### `PROWLARR_INDEXER_TIMEOUT`
**Indexer Search Timeout (seconds)**
How long to wait for a single indexer to answer a search. Indexers behind FlareSolverr can need 90 seconds or more while a cold Cloudflare challenge is solved; raise this if searches come back empty and the Prowlarr log shows the search still running.
- **Type:** number
- **Default:** `90`
- **Constraints:** min: 5, max: 300
#### `PROWLARR_AUTO_EXPAND`
**Auto-expand search on no results**
@@ -1159,6 +1319,15 @@ Automatically retry search without category filtering if no results are found
- **Type:** boolean
- **Default:** `false`
#### `PROWLARR_COLLAPSE_DUPLICATES`
**Show one row per release**
Collapse a release that several indexer entries returned down to a single row, keeping the entry with the best Prowlarr priority. Turn this off to see every entry that carried it, which is what makes results from filter-specific entries (freeleech and the like) visible.
- **Type:** boolean
- **Default:** `true`
#### `PROWLARR_USE_SEED_PREFERENCES`
**Use Prowlarr seed preferences**
@@ -1175,8 +1344,11 @@ Apply per-indexer seed time and ratio preferences from Prowlarr when sending tor
| Variable | Description | Type | Default |
|----------|-------------|------|---------|
| `NEWZNAB_ENABLED` | Enable searching for books via a Newznab-compatible indexer | boolean | `false` |
| `NEWZNAB_URL` | Base URL of your Newznab indexer or aggregator | string | _none_ |
| `NEWZNAB_API_KEY` | Your Newznab API key (leave blank if not required) | string (secret) | _none_ |
| `NEWZNAB_INDEXERS` | Named Newznab connections. Each row accepts `name`, `url`, and `api_key`. | JSON array | `[]` |
| `NEWZNAB_URL` | Legacy single-indexer URL, used when `NEWZNAB_INDEXERS` is empty | string | _none_ |
| `NEWZNAB_API_KEY` | Legacy single-indexer API key | string (secret) | _none_ |
| `NEWZNAB_EBOOK_CATEGORIES` | Newznab category IDs searched for ebooks. Most indexers use the standard 7000, but some use custom IDs. Leave empty to use 7000. | string (comma-separated) | `7000` |
| `NEWZNAB_AUDIOBOOK_CATEGORIES` | Newznab category IDs searched for audiobooks. Most indexers use the standard 3030, but some use custom IDs. Leave empty to use 3030. | string (comma-separated) | `3030` |
| `NEWZNAB_AUTO_EXPAND` | Automatically retry search without category filtering if no results are found | boolean | `false` |
<details>
@@ -1191,25 +1363,58 @@ Enable searching for books via a Newznab-compatible indexer
- **Type:** boolean
- **Default:** `false`
#### `NEWZNAB_INDEXERS`
**Named Indexers**
Configure multiple named Newznab-compatible indexers. The name is shown beside each search result. For environment-based configuration, provide a JSON array:
```json
[
{"name":"NZBGeek","url":"https://api.nzbgeek.info","api_key":"..."},
{"name":"DrunkenSlug","url":"https://drunkenslug.com","api_key":"..."}
]
```
- **Type:** JSON array
- **Default:** `[]`
#### `NEWZNAB_URL`
**Newznab URL**
**Legacy Newznab URL**
Base URL of your Newznab indexer or aggregator
Single-indexer fallback used only when `NEWZNAB_INDEXERS` is empty.
- **Type:** string
- **Default:** _none_
- **Required:** Yes
#### `NEWZNAB_API_KEY`
**API Key**
**Legacy API Key**
Your Newznab API key (leave blank if not required)
API key for the legacy Newznab URL.
- **Type:** string (secret)
- **Default:** _none_
#### `NEWZNAB_EBOOK_CATEGORIES`
**Ebook Categories**
Newznab category IDs searched for ebooks. Most indexers use the standard 7000, but some use custom IDs. Leave empty to use 7000.
- **Type:** string (comma-separated)
- **Default:** `7000`
#### `NEWZNAB_AUDIOBOOK_CATEGORIES`
**Audiobook Categories**
Newznab category IDs searched for audiobooks. Most indexers use the standard 3030, but some use custom IDs. Leave empty to use 3030.
- **Type:** string (comma-separated)
- **Default:** `3030`
#### `NEWZNAB_AUTO_EXPAND`
**Auto-expand search on no results**
@@ -1291,9 +1496,11 @@ Delay between requests in seconds to avoid rate limiting (0-10).
| `IRC_SERVER` | IRC server hostname | string | _none_ |
| `IRC_PORT` | IRC server port (usually 6697 for TLS, 6667 for plain) | number | `6697` |
| `IRC_USE_TLS` | Enable TLS/SSL encryption for the IRC connection. Disable for servers that don't support TLS. | boolean | `true` |
| `IRC_CHANNEL` | Channel name without the # prefix | string | _none_ |
| `IRC_CHANNEL` | Channel name without the # prefix. Used for all searches unless a separate audiobook channel is configured below. | string | _none_ |
| `IRC_NICK` | Your IRC nickname (required). Must be unique on the IRC network. | string | _none_ |
| `IRC_SEARCH_BOT` | The search bot to address queries to (required). | string | _none_ |
| `IRC_SEARCH_BOT` | The search bot to address queries to (required). Searches are sent as "@<bot> <query>". | string | _none_ |
| `IRC_AUDIOBOOK_CHANNEL` | Optional. Channel name (without the # prefix) for networks that index audiobooks separately, such as Undernet's bookz. Leave blank (the usual setting) to search the main channel above for audiobooks too. | string | _none_ |
| `IRC_AUDIOBOOK_SEARCH_BOT` | Optional. Search bot for the audiobook channel. Leave blank to reuse the main search bot above. Only used when an audiobook channel is set. | string | _none_ |
| `IRC_CACHE_TTL` | How long to keep cached search results before they expire. | string (choice) | `2592000` |
<details>
@@ -1331,7 +1538,7 @@ Enable TLS/SSL encryption for the IRC connection. Disable for servers that don't
**Channel**
Channel name without the # prefix
Channel name without the # prefix. Used for all searches unless a separate audiobook channel is configured below.
- **Type:** string
- **Default:** _none_
@@ -1351,12 +1558,30 @@ Your IRC nickname (required). Must be unique on the IRC network.
**Search bot**
The search bot to address queries to (required). Searches are sent as "@<bot> <query>". Without it, queries would be posted unaddressed to the channel.
The search bot to address queries to (required). Searches are sent as "@<bot> <query>".
- **Type:** string
- **Default:** _none_
- **Required:** Yes
#### `IRC_AUDIOBOOK_CHANNEL`
**Audiobook channel**
Optional. Channel name (without the # prefix) for networks that index audiobooks separately, such as Undernet's bookz. Leave blank (the usual setting) to search the main channel above for audiobooks too.
- **Type:** string
- **Default:** _none_
#### `IRC_AUDIOBOOK_SEARCH_BOT`
**Audiobook search bot**
Optional. Search bot for the audiobook channel. Leave blank to reuse the main search bot above. Only used when an audiobook channel is set.
- **Type:** string
- **Default:** _none_
#### `IRC_CACHE_TTL`
**Cache Duration**
@@ -1374,9 +1599,13 @@ How long to keep cached search results before they expire.
| Variable | Description | Type | Default |
|----------|-------------|------|---------|
| `PROWLARR_TORRENT_CLIENT` | Choose which torrent client to use | string (choice) | _empty string_ |
| `ALLDEBRID_API_KEY` | AllDebrid API Key (apiv4) from your AllDebrid account settings | string (secret) | _none_ |
| `REALDEBRID_API_KEY` | Real-Debrid API Key (Secret Token) from your Real-Debrid account settings | string (secret) | _none_ |
| `TORBOX_API_KEY` | TorBox API Key from your TorBox account settings | string (secret) | _none_ |
| `QBITTORRENT_URL` | Web UI URL of your qBittorrent instance | string | _none_ |
| `QBITTORRENT_USERNAME` | qBittorrent Web UI username | string | _none_ |
| `QBITTORRENT_PASSWORD` | qBittorrent Web UI password | string (secret) | _none_ |
| `QBITTORRENT_API_KEY` | Found in qBittorrent: Options > Web UI > API Key (qBittorrent 5.2.0+). Used instead of the username and password when set. | string (secret) | _none_ |
| `QBITTORRENT_CATEGORY` | Category to assign to book downloads in qBittorrent | string | `books` |
| `QBITTORRENT_CATEGORY_AUDIOBOOK` | Category for audiobook downloads. Leave empty to use the book category. | string | _empty string_ |
| `QBITTORRENT_DOWNLOAD_DIR` | Server-side directory where torrents are downloaded (optional, uses qBittorrent default if not specified) | string | _none_ |
@@ -1396,9 +1625,11 @@ How long to keep cached search results before they expire.
| `RTORRENT_URL` | XML-RPC URL of your rTorrent instance | string | _none_ |
| `RTORRENT_USERNAME` | HTTP Basic auth username (if authentication enabled) | string | _none_ |
| `RTORRENT_PASSWORD` | HTTP Basic auth password | string (secret) | _none_ |
| `RTORRENT_LABEL` | Label to assign to book downloads in rTorrent | string | `cwabd` |
| `RTORRENT_LABEL` | Label to assign to ebook downloads in rTorrent | string | `cwabd` |
| `RTORRENT_AUDIOBOOK_LABEL` | Label to assign to audiobook downloads in rTorrent (falls back to Book Label if not set) | string | _none_ |
| `RTORRENT_DOWNLOAD_DIR` | Server-side directory where torrents are downloaded (optional, uses rTorrent default if not specified) | string | _none_ |
| `PROWLARR_TORRENT_ACTION` | Remove deletes the torrent from your client immediately after import (stops seeding, files are kept); Keep leaves it in the client to continue seeding | string (choice) | `keep` |
| `PROWLARR_TORRENT_ACTION` | Choose whether to keep, remove, or move the torrent to another category or label after import | string (choice) | `keep` |
| `PROWLARR_TORRENT_POST_IMPORT_CATEGORY` | Category or label to assign after a successful import | string | _empty string_ |
| `PROWLARR_USENET_CLIENT` | Choose which usenet client to use | string (choice) | _empty string_ |
| `NZBGET_URL` | URL of your NZBGet instance | string | _none_ |
| `NZBGET_USERNAME` | NZBGet control username | string | `nzbget` |
@@ -1422,7 +1653,34 @@ Choose which torrent client to use
- **Type:** string (choice)
- **Default:** _empty string_
- **Options:** `""` (None), `qbittorrent` (qBittorrent), `transmission` (Transmission), `deluge` (Deluge), `rtorrent` (rTorrent)
- **Options:** `""` (None), `alldebrid` (AllDebrid), `qbittorrent` (qBittorrent), `realdebrid` (Real-Debrid), `torbox` (TorBox), `transmission` (Transmission), `deluge` (Deluge), `rtorrent` (rTorrent)
#### `ALLDEBRID_API_KEY`
**API Key**
AllDebrid API Key (apiv4) from your AllDebrid account settings
- **Type:** string (secret)
- **Default:** _none_
#### `REALDEBRID_API_KEY`
**API Key**
Real-Debrid API Key (Secret Token) from your Real-Debrid account settings
- **Type:** string (secret)
- **Default:** _none_
#### `TORBOX_API_KEY`
**API Key**
TorBox API Key from your TorBox account settings
- **Type:** string (secret)
- **Default:** _none_
#### `QBITTORRENT_URL`
@@ -1451,6 +1709,15 @@ qBittorrent Web UI password
- **Type:** string (secret)
- **Default:** _none_
#### `QBITTORRENT_API_KEY`
**API Key**
Found in qBittorrent: Options > Web UI > API Key (qBittorrent 5.2.0+). Used instead of the username and password when set.
- **Type:** string (secret)
- **Default:** _none_
#### `QBITTORRENT_CATEGORY`
**Book Category**
@@ -1626,11 +1893,20 @@ HTTP Basic auth password
**Book Label**
Label to assign to book downloads in rTorrent
Label to assign to ebook downloads in rTorrent
- **Type:** string
- **Default:** `cwabd`
#### `RTORRENT_AUDIOBOOK_LABEL`
**Audiobook Label**
Label to assign to audiobook downloads in rTorrent (falls back to Book Label if not set)
- **Type:** string
- **Default:** _none_
#### `RTORRENT_DOWNLOAD_DIR`
**Download Directory**
@@ -1644,11 +1920,20 @@ Server-side directory where torrents are downloaded (optional, uses rTorrent def
**Torrent Completion Action**
Remove deletes the torrent from your client immediately after import (stops seeding, files are kept); Keep leaves it in the client to continue seeding
Choose whether to keep, remove, or move the torrent to another category or label after import
- **Type:** string (choice)
- **Default:** `keep`
- **Options:** `keep` (Keep), `remove` (Remove)
- **Options:** `keep` (Keep), `remove` (Remove), `change_category` (Change Category)
#### `PROWLARR_TORRENT_POST_IMPORT_CATEGORY`
**Post-Import Category**
Category or label to assign after a successful import
- **Type:** string
- **Default:** _empty string_
#### `PROWLARR_USENET_CLIENT`
@@ -1760,7 +2045,7 @@ Move deletes the job from your usenet client after import; Copy keeps it in the
| Variable | Description | Type | Default |
|----------|-------------|------|---------|
| `HARDCOVER_ENABLED` | Enable Hardcover as a metadata provider for book searches | boolean | `false` |
| `HARDCOVER_API_KEY` | Get your API key from hardcover.app/account/api | string (secret) | _none_ |
| `HARDCOVER_API_KEY` | Get your API key from hardcover.app/account/api (starts with hc_pat_) | string (secret) | _none_ |
| `HARDCOVER_DEFAULT_SORT` | Default sort order for Hardcover search results. | string (choice) | `relevance` |
| `HARDCOVER_EXCLUDE_COMPILATIONS` | Filter out compilations, anthologies, and omnibus editions from search results | boolean | `false` |
| `HARDCOVER_EXCLUDE_UNRELEASED` | Filter out books with a release year in the future | boolean | `false` |
@@ -1782,7 +2067,7 @@ Enable Hardcover as a metadata provider for book searches
**API Key**
Get your API key from hardcover.app/account/api
Get your API key from hardcover.app/account/api (starts with hc_pat_)
- **Type:** string (secret)
- **Default:** _none_
@@ -1900,6 +2185,26 @@ Default sort order for Google Books search results.
</details>
### Metadata Providers: Moly.hu
| Variable | Description | Type | Default |
|----------|-------------|------|---------|
| `MOLY_ENABLED` | Enable Moly.hu as a metadata provider for book searches | boolean | `false` |
<details>
<summary>Detailed descriptions</summary>
#### `MOLY_ENABLED`
**Enable Moly.hu**
Enable Moly.hu as a metadata provider for book searches
- **Type:** boolean
- **Default:** `false`
</details>
## Direct Download
### Direct Download: Download Sources
@@ -1907,11 +2212,13 @@ Default sort order for Google Books search results.
| Variable | Description | Type | Default |
|----------|-------------|------|---------|
| `DIRECT_DOWNLOAD_ENABLED` | Show Direct Download in release-source lists and allow Direct mode searches. Add your own mirror URLs in the Mirrors tab before using it. | boolean | `false` |
| `DIRECT_DOWNLOAD_LANGUAGE_FROM_PATH` | When language metadata is missing or unknown, parse the distant path (file path shown in search results) for language tags like [BD FR] or [En]. Also enables local language filtering so lgli files without AA language metadata are not excluded before the distant path can be checked. | boolean | `false` |
| `AA_DONATOR_KEY` | Enables fast download access on AA. Get this from your donator account page. | string (secret) | _none_ |
| `FAST_SOURCES_DISPLAY` | Always tried first, no waiting or bypass required. | JSON array | _see UI for defaults_ |
| `SOURCE_PRIORITY` | Fallback sources, may have waiting. Requires bypasser. Drag to reorder. | JSON array | _see UI for defaults_ |
| `MAX_RETRY` | Maximum retry attempts for failed downloads. | number | `10` |
| `DEFAULT_SLEEP` | Wait time between download retry attempts. | number | `5` |
| `RELEASE_SEARCH_TIMEOUT` | How long one release search may run before it gives up and reports why. A first search on a cold start pays for a browser solve, so leave room for one. If you use a reverse proxy, its read timeout should be at least this high or it will cut the search off with a 504 first. | number | `300` |
| `AA_CONTENT_TYPE_ROUTING` | Override destination based on content type metadata. | boolean | `false` |
| `AA_CONTENT_TYPE_DIR_FICTION` | Fiction Books | string | _none_ |
| `AA_CONTENT_TYPE_DIR_NON_FICTION` | Non-Fiction Books | string | _none_ |
@@ -1934,6 +2241,15 @@ Show Direct Download in release-source lists and allow Direct mode searches. Add
- **Type:** boolean
- **Default:** `false`
#### `DIRECT_DOWNLOAD_LANGUAGE_FROM_PATH`
**Detect Language From Distant Path**
When language metadata is missing or unknown, parse the distant path (file path shown in search results) for language tags like [BD FR] or [En]. Also enables local language filtering so lgli files without AA language metadata are not excluded before the distant path can be checked.
- **Type:** boolean
- **Default:** `false`
#### `AA_DONATOR_KEY`
**Account Donator Key**
@@ -1981,6 +2297,16 @@ Wait time between download retry attempts.
- **Default:** `5`
- **Constraints:** min: 1, max: 60
#### `RELEASE_SEARCH_TIMEOUT`
**Release Search Timeout (seconds)**
How long one release search may run before it gives up and reports why. A first search on a cold start pays for a browser solve, so leave room for one. If you use a reverse proxy, its read timeout should be at least this high or it will cut the search off with a 504 first.
- **Type:** number
- **Default:** `300`
- **Constraints:** min: 30, max: 1800
#### `AA_CONTENT_TYPE_ROUTING`
**Enable Content-Type Routing**
@@ -2057,6 +2383,8 @@ Override destination based on content type metadata.
| `EXT_BYPASSER_URL` | URL of the external bypasser service (e.g., FlareSolverr). | string | `http://flaresolverr:8191` |
| `EXT_BYPASSER_PATH` | API path for the external bypasser. | string | `/v1` |
| `EXT_BYPASSER_TIMEOUT` | Timeout for external bypasser requests in milliseconds. | number | `60000` |
| `BYPASS_PAGE_SOURCE_TIMEOUT` | How long to wait for a solved page to produce its content before the bypass is retried. Raise it if solves succeed but searches still fail. | number | `20` |
| `BYPASS_BROWSER_IDLE_TIMEOUT` | How long the bypass helper process may sit unused before it is shut down. Higher keeps more searches fast, lower frees memory sooner. | number | `180` |
<details>
<summary>Detailed descriptions</summary>
@@ -2112,6 +2440,27 @@ Timeout for external bypasser requests in milliseconds.
- **Requires restart:** Yes
- **Constraints:** min: 10000, max: 300000
#### `BYPASS_PAGE_SOURCE_TIMEOUT`
**Page Read Timeout (seconds)**
How long to wait for a solved page to produce its content before the bypass is retried. Raise it if solves succeed but searches still fail.
- **Type:** number
- **Default:** `20`
- **Constraints:** min: 1, max: 120
#### `BYPASS_BROWSER_IDLE_TIMEOUT`
**Bypasser Idle Timeout (seconds)**
How long the bypass helper process may sit unused before it is shut down. Higher keeps more searches fast, lower frees memory sooner.
- **Type:** number
- **Default:** `180`
- **Requires restart:** Yes
- **Constraints:** min: 30, max: 3600
</details>
### Direct Download: Mirrors
+1
View File
@@ -15,6 +15,7 @@ Use the guides below to set up the app, connect your library tools, and understa
- [Users & Requests](users-and-requests.md)
- [Reverse Proxy](reverse-proxy.md)
- [OIDC](oidc.md)
- [API Access](api-access.md)
- [URL Search Parameters](url-search-parameters.md)
- [Custom Scripts](custom-scripts.md)
+17 -3
View File
@@ -12,7 +12,7 @@ With a subpath (`URL_BASE=/shelfmark/`):
https://<your-shelfmark-domain>/shelfmark/api/auth/oidc/callback
```
The callback URL is constructed from the incoming request, so your reverse proxy must forward `X-Forwarded-Proto` and `X-Forwarded-Host` correctly. PKCE (S256) is used automatically.
The callback URL is constructed from the incoming request, so your reverse proxy must forward `X-Forwarded-Proto` and `X-Forwarded-Host` correctly, including the external port when it is not the protocol default. PKCE (S256) is used automatically.
## Settings
@@ -30,7 +30,19 @@ Configure in **Settings → Security → Authentication Method → OIDC**.
| Auto-Provision Users | Create accounts on first login | `true` |
| Login Button Label | Custom text for the sign-in button | — |
Use **Test Connection** to verify discovery and client configuration before attempting login.
Use **Test Connection** to verify discovery, client configuration, and the provider's token signing keys (JWKS) before attempting login.
> **Authentik users:** make sure your provider has a **Signing Key** selected (e.g. the default self-signed certificate). Without one, Authentik serves an empty JWKS document and every login fails with an OIDC callback error, even though the discovery document looks healthy.
## Account Linking
On login, Shelfmark matches the OIDC identity to a user account in this order:
1. **OIDC subject** — a user who has logged in through this provider before.
2. **Email** — a local account with the same (unique) email address. This only happens when the provider also asserts `email_verified: true` for the address; an unverified email would let anyone claim a local account by registering its address at the IdP.
3. Otherwise, a new account is created when **Auto-Provision Users** is enabled (username conflicts get a numeric suffix), or the login is rejected with "Account not found" when it is disabled.
If the `email_verified` claim is missing or `false`, email linking is silently skipped — a common surprise when the address was never verified at the identity provider (e.g. Keycloak's **Email verified** toggle on the user, or Authentik accounts created without email verification). Make sure the `email` scope is requested and the address is marked verified in your IdP.
## Environment Variables
@@ -46,6 +58,8 @@ If `DISABLE_LOCAL_AUTH` and `OIDC_AUTO_REDIRECT` are both enabled, users are red
## Troubleshooting
- **No token signing keys (empty JWKS)** — The provider's JWKS endpoint returned no keys, so ID tokens can't be verified. In Authentik this happens when the provider has no **Signing Key** selected; pick one (e.g. the default self-signed certificate) and try again.
- **Issuer validation failed** — The issuer in the token doesn't match the discovery document. Check your provider's external URL / issuer configuration.
- **Callback URL mismatch** — Reverse proxy isn't forwarding `X-Forwarded-Proto` or `X-Forwarded-Host`, so the constructed callback URL doesn't match what's registered in the provider.
- **Account not found** — Auto-provision is disabled and the user hasn't been pre-created by an admin.
- **Account not found** — Auto-provision is disabled and the user hasn't been pre-created by an admin. If you pre-created the account with a matching email, see [Account Linking](#account-linking): the provider must send `email_verified: true` for linking to happen.
- **Login created a duplicate account instead of linking to my local one** — Email linking requires a verified email; see [Account Linking](#account-linking). With `DEBUG=true`, the log notes when linking is skipped because the address isn't verified.
+8 -5
View File
@@ -23,10 +23,11 @@ server {
location / {
proxy_pass http://shelfmark:8084;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
@@ -56,11 +57,11 @@ All Shelfmark paths (UI, API, assets, Socket.IO) are served under the base path.
location /shelfmark/ {
proxy_pass http://shelfmark:8084/shelfmark/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 86400;
@@ -86,6 +87,7 @@ Configure in Settings → Security:
| Proxy Auth Logout URL | `https://auth.example.com/logout` |
| Proxy Auth Admin Group Header | `Remote-Groups` |
| Proxy Auth Admin Group Name | `admins` (or your admin group) |
| Proxy Auth Default Role | `User` — first-time users are regular users; the very first account is still made admin. Only consulted when no admin group is set |
#### Nginx Configuration with Authelia
@@ -136,11 +138,11 @@ location /shelfmark/ {
proxy_pass http://shelfmark:8084/shelfmark/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 86400;
@@ -158,6 +160,7 @@ If login, settings saves, or downloads appear to fail in the browser but the act
- Do not force `Connection: upgrade` on every request. That can break normal `POST` and `PUT` responses while the backend still processes them.
- If your proxy UI does not support conditional websocket headers, remove the forced websocket headers entirely and let Shelfmark fall back to polling.
- Keep the standard forwarded headers: `Host`, `X-Forwarded-For`, `X-Forwarded-Proto`, and `X-Forwarded-Host` when using a subpath or OIDC.
- Preserve the original port in `Host` and `X-Forwarded-Host` by using `$http_host` rather than `$host` when Shelfmark is exposed on a custom port.
This is especially relevant for Nginx Proxy Manager or custom advanced config snippets that add websocket headers globally.
+46 -23
View File
@@ -1,71 +1,79 @@
# URL Search Parameters
You can trigger searches directly via URL by adding query parameters. This enables bookmarking searches and sharing links.
You can trigger searches directly via URL. This enables bookmarking searches and sharing links.
Parameters live in the URL **hash** (`#…`), so they stay in the browser and are never sent to
the server. Shelfmark also keeps the hash in sync as you search, so the address bar always
holds a shareable link to what you're looking at.
## Basic Usage
```
http://your-server:8084/?q=harry+potter
http://your-server:8084/#q=harry+potter
```
Older query-string links (`/?q=harry+potter`) still work: they're read once on load and
rewritten to the hash form.
## Supported Parameters
| Parameter | Description | Example |
|-----------|-------------|---------|
| `q` or `query` | Main search query | `/?q=dune` |
| `author` | Filter by author name | `/?author=frank+herbert` |
| `title` | Filter by book title | `/?title=foundation` |
| `isbn` | Filter by ISBN | `/?isbn=978-0747532699` |
| `lang` | Filter by language (ISO 639-1 code) | `/?lang=en` |
| `format` | Filter by file format | `/?format=epub` |
| `content` | Filter by content type | `/?content=fiction` |
| `content_type` | Select media type (`ebook`, `audiobook`, or `combined`) in Universal mode only | `/?q=dune&content_type=audiobook` |
| `sort` | Sort order for results | `/?sort=newest` |
| `q` or `query` | Main search query | `/#q=dune` |
| `author` | Filter by author name | `/#author=frank+herbert` |
| `title` | Filter by book title | `/#title=foundation` |
| `isbn` | Filter by ISBN | `/#isbn=978-0747532699` |
| `lang` | Filter by language (ISO 639-1 code) | `/#lang=en` |
| `format` | Filter by file format | `/#format=epub` |
| `content` | Filter by content type | `/#content=fiction` |
| `content_type` | Select media type (`ebook`, `audiobook`, or `combined`) in Universal mode only | `/#q=dune&content_type=audiobook` |
| `sort` | Sort order for results | `/#sort=newest` |
| `search_by` | "Search By" target the query applies to (`general`, `author`, `title`, `isbn`, a metadata provider field like `series`, or `manual`) | `/#search_by=author&q=frank+herbert` |
## Multiple Values
Some parameters support multiple values by repeating the parameter:
```
/?lang=en&lang=de&lang=fr
/?format=epub&format=mobi&format=azw3
/#lang=en&lang=de&lang=fr
/#format=epub&format=mobi&format=azw3
```
## Examples
**Simple search:**
```
/?q=lord+of+the+rings
/#q=lord+of+the+rings
```
**Search with author filter:**
```
/?q=dune&author=frank+herbert
/#q=dune&author=frank+herbert
```
**Search with format and language:**
```
/?q=harry+potter&format=epub&lang=en
/#q=harry+potter&format=epub&lang=en
```
**Author search with multiple formats:**
```
/?author=stephen+king&format=epub&format=mobi
/#author=stephen+king&format=epub&format=mobi
```
**Search with sort order:**
```
/?q=science+fiction&sort=newest
/#q=science+fiction&sort=newest
```
**Universal search as audiobook:**
```
/?q=dune&content_type=audiobook
/#q=dune&content_type=audiobook
```
**Universal search forcing combined (ebook + audiobook):**
```
/?q=dune&content_type=combined
/#q=dune&content_type=combined
```
## Search Mode Behavior
@@ -77,13 +85,28 @@ When Search Mode is set to Direct, all parameters are used to filter results fro
### Universal Mode
`q`, `sort`, and `content_type` are used. Other parameters (author, title, format, etc.) are silently ignored since metadata providers have their own search capabilities.
`q`, `search_by`, `sort`, and `content_type` are used. Other parameters (author, title, format, etc.) are silently ignored since metadata providers have their own search capabilities — except when `search_by` names one of the provider's own search fields, in which case `q` is sent as that field's value.
`content_type=combined` forces combined mode (search ebook and audiobook providers together), overriding the last-used preference. It is silently ignored if combined mode is unavailable (e.g. the combined selector is disabled in settings, or either content type is blocked by request policy).
## Search By
`search_by` picks which target the `q` value is applied to, matching the selector next to the
search box. It can be deep-linked on its own (`/#search_by=manual`) to open the app in that
mode with an empty query.
`search_by=manual` fills the search box but does not auto-run: manual search opens the release
browser from an explicit submit.
A `search_by` naming a target that isn't available (wrong search mode, or a metadata provider
that doesn't offer that field) is ignored, and the query falls back to a general search.
## Notes
- URL parameters are read once on page load
- The URL is not updated when you perform searches manually
- URL parameters are read once on page load, and again if the hash is replaced in an open tab
(e.g. pasting a shared link into the address bar)
- The hash is kept in sync with the search box, Search By target and filters as you search
- Spaces should be encoded as `+` or `%20`
- Invalid or unknown parameters are silently ignored
- Your last-used Search By target is remembered in browser storage and used when a link
doesn't specify one
+10 -1
View File
@@ -30,7 +30,7 @@ Requires mounting your Calibre-Web `app.db` to `/auth/app.db`.
Admins can configure per-user settings by editing a user in the user management panel. Non-admin users can also edit their own settings through **My Account** (accessible from the user menu). Admins control which sections are visible in My Account via the **Visible Self-Settings Sections** option.
There are three categories of per-user settings:
There are four categories of per-user settings:
### Delivery Preferences
@@ -42,6 +42,15 @@ Override where a user's downloads are sent. Options depend on the global output
- **BookLore library/path** — Per-user BookLore target (when using BookLore output mode)
- **Email recipient** — Per-user email address (when using Email output mode)
### Search Preferences
Override how a user searches, on top of the global search defaults:
- **Search mode** — Direct or Universal for this user
- **Default book languages** — The languages a user's searches fall back to when they don't pick one themselves. Useful for a shared instance where readers want different languages.
- **Metadata providers** — Book, audiobook, and combined-mode provider for this user
- **Default release sources** — The release tab opened first for books and audiobooks
### Notifications
Users can configure personal notification routes, separate from the global notification settings. Each route targets a URL (e.g. an Apprise-compatible endpoint) and can be scoped to specific event types or all events.
+45 -3
View File
@@ -81,6 +81,13 @@ if is_truthy "$ENABLE_LOGGING_VALUE"; then
fi
fi
# Egress modes are mutually exclusive. Check this BEFORE starting either one so
# we never run tor.sh and then abort, leaving a half-configured network stack.
if [ "$USING_TOR" = "true" ] && [ "$USING_WIREGUARD" = "true" ]; then
echo "USING_TOR and USING_WIREGUARD are mutually exclusive; enable only one egress mode." >&2
exit 1
fi
if [ "$USING_TOR" = "true" ]; then
if [ "$RUN_AS_NON_ROOT" = "true" ]; then
echo "USING_TOR=true requires the container to start as root." >&2
@@ -90,6 +97,15 @@ if [ "$USING_TOR" = "true" ]; then
./tor.sh
fi
if [ "$USING_WIREGUARD" = "true" ]; then
if [ "$RUN_AS_NON_ROOT" = "true" ]; then
echo "USING_WIREGUARD=true requires the container to start as root." >&2
echo "Non-root mode skips the privileged network setup WireGuard depends on." >&2
exit 1
fi
./wireguard.sh
fi
if [ "$FILE_LOGGING_ENABLED" = "true" ]; then
start_file_logging "$LOG_FILE"
fi
@@ -235,13 +251,22 @@ test_write() {
return 1
fi
if ! run_as_target_user sh -c 'echo 0123456789_TEST > "$1"' _ "$test_file"; then
# This is a probe: a failure here is expected (e.g. a fresh root-owned bind
# mount) and is recovered by the caller via change_ownership + re-probe. Hide
# the shell's "Permission denied"/"Read-only file system" stderr so a handled
# probe miss doesn't masquerade as a real boot failure in the logs.
if ! run_as_target_user sh -c 'echo 0123456789_TEST 2>/dev/null > "$1"' _ "$test_file"; then
echo "Failed to write test file in $folder as $USERNAME"
return 1
fi
FILE_CONTENT=$(cat "$test_file" 2>/dev/null || echo "")
rm -f "$test_file"
# A folder can be writable but not deletable (e.g. a Synology share without
# "Delete subfolders and files"). That is not a boot failure - the app writes
# files in place on such shares - so don't let a failed cleanup print an
# alarming error or fail the probe.
run_as_target_user rm -f "$test_file" 2>/dev/null || \
echo "Note: could not remove test file in $folder (folder is writable but not deletable)"
[ "$FILE_CONTENT" = "0123456789_TEST" ]
result=$?
if [ $result -eq 0 ]; then
@@ -448,12 +473,29 @@ else
if [ $config_ok -ne 0 ]; then
fail_unwritable_config_dir "$CONFIG_PATH"
fi
# The ingest/destination library (default /books) is user data and may be a
# bind mount owned by another uid; downloads fail with "Destination not
# writable" if the runtime user can't write there. Fix the top-level dir only
# (root mode) so we don't recursively chown a potentially huge library.
make_writable "${INGEST_DIR:-/books}" root
fi
# Always run Gunicorn (even when DEBUG=true) to ensure Socket.IO WebSocket
# upgrades work reliably on customer machines.
# Map app LOG_LEVEL (often DEBUG/INFO/...) to gunicorn's --log-level (lowercase).
gunicorn_loglevel=$([ "$DEBUG" = "true" ] && echo debug || echo "${LOG_LEVEL:-info}" | tr '[:upper:]' '[:lower:]')
# Gunicorn rejects anything outside its own list, so normalize and fall back to
# info rather than letting a typo stop the container from booting.
if [ "$DEBUG" = "true" ]; then
gunicorn_loglevel=debug
else
gunicorn_loglevel=$(echo "${LOG_LEVEL:-info}" | tr '[:upper:]' '[:lower:]')
[ "$gunicorn_loglevel" = "warn" ] && gunicorn_loglevel=warning
case "$gunicorn_loglevel" in
debug|info|warning|error|critical) ;;
*) gunicorn_loglevel=info ;;
esac
fi
command="${GUNICORN_BIN} --log-level ${gunicorn_loglevel} --access-logfile - --error-logfile - --worker-class geventwebsocket.gunicorn.workers.GeventWebSocketWorker --workers 1 -t 300 -b ${FLASK_HOST:-0.0.0.0}:${FLASK_PORT:-8084} shelfmark.main:app"
# If DEBUG and not using an external bypass
+9 -6
View File
@@ -19,28 +19,31 @@ dependencies = [
"psutil",
"emoji",
"rarfile",
"qbittorrent-api",
"qbittorrent-api>=2026.8.1",
"transmission-rpc",
"authlib>=1.7.2,<1.8",
"apprise>=1.11.0",
"authlib>=1.8.0,<1.9",
"apprise>=1.13.1",
# HTTP/2 client for RFC 8484 DoH: quad9 rejects HTTP/1.1 outright (505), which
# requests cannot speak. See shelfmark/download/doh_wireformat.py.
"httpx[http2]>=0.28.1",
]
[project.optional-dependencies]
browser = [
"pyvirtualdisplay",
"pyautogui",
"seleniumbase==4.49.10",
"seleniumbase==4.54.9",
"python-xlib",
]
[dependency-groups]
dev = [
"basedpyright>=1.39.7",
"basedpyright>=1.40.1",
"prek",
"pytest",
"pytest-cov",
"pytest-xdist>=3.8.0",
"ruff==0.15.17",
"ruff==0.16.8",
"vulture>=2.14",
]
+78 -5
View File
@@ -3,7 +3,7 @@
<img src="src/frontend/public/logo.png" alt="Shelfmark" width="200">
> [!NOTE]
> This project is in a stable state as of May 2026 but is not under active maintenance.
> Shelfmark is feature stable and maintained on a best-effort basis. Bug fixes, security updates, and small quality-of-life improvements are still shipped, and pull requests are reviewed — including new features. There is no roadmap for new features for now.
Shelfmark is a self-hosted web interface for searching and requesting books and audiobooks across multiple sources. Bring your own sources, metadata providers, and download clients to build a single hub for your digital library. Supports multiple users with a built-in request system, so you can share your instance with others and let them browse and request books on their own.
@@ -44,6 +44,7 @@ Works great alongside the following library tools, with support for automatic im
### Prerequisites
- Docker & Docker Compose
- At least 2 GB of RAM available to the container when using the standard image — see [Memory Requirements](#memory-requirements)
### Installation
@@ -94,6 +95,30 @@ volumes:
- Aggregates releases from multiple configured sources
- Full audiobook support
### Hardcover API Key
Hardcover powers metadata search in Universal mode. Create a token at
[hardcover.app/account/api](https://hardcover.app/account/api) — current keys start with `hc_pat_`
and are far shorter than the JWTs Hardcover issued before August 2026.
Tick these seven scopes on the token screen:
| Scope | Used for |
|-------|----------|
| `read:catalog` | Metadata search, plus book, edition, author and series lookups |
| `read:library` | Your reading status and shelf counts |
| `read:lists` | Your lists and the books on them |
| `read:me:content` | Test Connection and the "Connected as" label |
| `read:users` | Usernames shown alongside lists |
| `write:library` | Setting a book's reading status from Shelfmark |
| `write:lists` | Adding and removing books from lists, including auto-remove on download |
The two `write:` scopes matter only if you set reading status from Shelfmark or leave
**Auto-Remove from List on Download** enabled (it is on by default) — without them those actions
fail silently. Everything else Hardcover offers (journal, goals, reviews, prompts, notifications,
account) can stay unticked. The `all` scope works too, but it grants full account access including
deletion, so prefer the list above.
### Environment Variables
Environment variables work for initial setup and Docker deployments. They serve as defaults that can be overridden in the web interface.
@@ -106,13 +131,24 @@ Environment variables work for initial setup and Docker deployments. They serve
| `PUID` / `PGID` | Runtime user/group for the default root-startup flow (also supports legacy `UID`/`GID`) | `1000` / `1000` |
| `SEARCH_MODE` | `direct` or `universal` | `universal` |
| `USING_TOR` | Enable Tor routing (requires root startup) | `false` |
| `USING_WIREGUARD` | Enable WireGuard VPN egress with kill-switch (requires root startup) | `false` |
| `WIREGUARD_CONFIG` | Path to the mounted wg-quick config | `/config/wg0.conf` |
| `WIREGUARD_INTERFACE` | WireGuard interface name | `wg0` |
| `LAN_NETWORK` | Comma-separated CIDRs kept off the tunnel so the WebUI / internal clients stay reachable | `127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16` |
| `WIREGUARD_ENFORCE_DNS` | Pin the resolver (via `WIREGUARD_DNS`, else the config's `DNS =`) so DNS can't silently fall back to an off-tunnel path. Designed for a trusted LAN resolver kept reachable via `LAN_NETWORK` (query leaves over the LAN; download still egresses via the tunnel) — it does **not** force queries through the tunnel. Docker's embedded resolver (`127.0.0.11`) is preserved when present so container-name resolution keeps working; pin its upstream via the container's `dns:` list. Fails closed if no resolver is available or `/etc/resolv.conf` is not writable. | `true` |
| `WIREGUARD_DNS` | Explicit resolver(s) to pin (comma/space separated). Use when the VPN's pushed DNS filters domains you need; point at a resolver reachable via the tunnel or an allowed LAN resolver. | _(unset; uses config `DNS =`)_ |
| `WIREGUARD_DISABLE_IPV6` | Strip IPv6 from the tunnel config (many container kernels lack the ip6tables `raw` table wg-quick needs) and remove IPv6 as a leak surface. | `true` |
| `WIREGUARD_ALLOW_IPV6_LEAK` | Escape hatch: continue even when an IPv6 kill-switch can't be installed AND IPv6 can't be disabled. Only set if the container has no IPv6 connectivity. | `false` |
| `WIREGUARD_ALLOW_WEBUI_OFFTUNNEL` | Opt-in off-tunnel WebUI reachability. Default (`false`) keeps the kill-switch strictly fail-closed: the only off-tunnel egress is loopback, the tunnel device and the LAN allowlist. Set `true` only if a **non-LAN** client (e.g. a public reverse proxy on another segment) must reach the WebUI; it permits app-server **replies** (`--sport FLASK_PORT`, conntrack REPLY) off-tunnel — server replies only, never client-initiated egress. LAN clients never need it (covered by `LAN_NETWORK`). | `false` |
| `WIREGUARD_STALE_AFTER` | Seconds since the last handshake before the healthcheck bounces the tunnel. | `180` |
See the full [Environment Variables Reference](docs/environment-variables.md) for all available options.
Some of the additional options available in Settings:
- **Prowlarr** - Configure indexers and download clients to download books and audiobooks
- **Additional audiobook sources** - Configure additional sources for audiobook discovery
- **IRC** - Add details for IRC book sources and download directly from the UI
- **Direct Download mirrors** - Supply your own Anna's Archive mirror URLs; Auto mode tries them in the order listed. The `annas-archive.is` domain does not currently work as a source — use `annas-archive.gl` instead (checked August 2026; mirror availability changes)
- **IRC** - Add details for IRC book sources and download directly from the UI. Most networks serve audiobooks from the same channel as ebooks (on `irc.irchighway.net` that's `#ebooks`, while `#bookz` is effectively inactive), so leave the separate audiobook channel blank unless your network actually indexes one. IRC audiobooks usually arrive as ZIP/RAR archives — keep those enabled under Supported Audiobook Formats or the releases are filtered out of results
- **Library Link** - Add a link to your Calibre-Web or Grimmory instance in the UI header
- **File processing** - Customiseable download paths, file renaming and directory creation with template-based renaming
- **Network Settings** - Custom proxy support (SOCKS5 + HTTP/S) and configurable DNS
@@ -128,6 +164,17 @@ docker compose up -d
The full-featured image with all network capabilities included.
#### Memory Requirements
The standard image ships a real Chromium browser, which it launches to solve Cloudflare challenges for Direct Download. Chromium needs room to run:
- **2 GB of RAM available to the container** is a safe minimum; 1 GB or less is where problems usually start
- Only relevant if you use Direct Download. Prowlarr, IRC and audiobook sources don't start the browser
When the container is starved of memory, Chromium fails to start and every Direct Download fails with unrelated-looking errors — repeated `403 detected; switching to bypasser` followed by `No download URL found`, and downloads that never complete. If you're seeing that, check the container's memory limit and the host's free memory before suspecting your ISP or DNS.
If you can't spare the memory, use the [Lite](#lite) image with an external resolver (e.g. FlareSolverr) running elsewhere.
#### Tor Routing
Optional Tor support for network privacy:
```bash
@@ -141,12 +188,31 @@ docker compose -f docker-compose.tor.yml up -d
- Timezone is auto-detected from Tor exit node
- Custom DNS/proxy settings are ignored when Tor is active
#### WireGuard VPN Routing
Optional WireGuard support to route all external egress through a VPN tunnel with a fail-closed kill-switch:
```bash
curl -O https://raw.githubusercontent.com/calibrain/shelfmark/main/compose/docker-compose.wireguard.yml
# place your wg-quick config where the compose mounts /config, as wg0.conf
docker compose -f docker-compose.wireguard.yml up -d
```
**Notes:**
- Requires root startup
- Requires `NET_ADMIN` and `NET_RAW` capabilities
- Mount a standard wg-quick config at `WIREGUARD_CONFIG` (default `/config/wg0.conf`)
- All non-LAN egress is forced through the tunnel; if the tunnel drops, external traffic **fails closed** while LAN ranges (WebUI, Prowlarr, qBittorrent) stay reachable
- IPv4 and IPv6 both fail closed. On kernels without a usable `ip6tables`, disable IPv6 for the container (`sysctls: net.ipv6.conf.all.disable_ipv6=1`, as in the compose example) or the container refuses to start rather than risk an IPv6 leak
- A supervised healthcheck bounces the tunnel if the handshake goes stale, and refreshes the endpoint allow rules so a roaming/rotated peer endpoint can reconnect
- Mutually exclusive with `USING_TOR`
- **DNS trust:** `WIREGUARD_DNS` must be a resolver you trust on a trusted network segment. When it is a LAN resolver (kept reachable off-tunnel by `LAN_NETWORK`), the query to that resolver leaves as plaintext UDP/53 on the LAN — the resolver is responsible for encrypting upstream. Two resolver paths exist: (1) when Docker's embedded resolver (`127.0.0.11`) is present it is **preserved** so container names (Prowlarr, qBittorrent) resolve — you MUST pin its upstream to a trusted resolver via the container's compose `dns:` list, since `WIREGUARD_DNS` cannot repoint the embedded resolver from inside the container; (2) otherwise `WIREGUARD_DNS`/the config `DNS =` line is written to `/etc/resolv.conf`. Setting `WIREGUARD_ENFORCE_DNS=false` is a **foot-gun**: with no embedded resolver present the container then uses its inherited resolver, which forwards to the Docker daemon's upstream **off-tunnel**, leaking your DNS. Leave enforcement on unless you have pinned the resolver another way.
### Lite
A lighter image without the built-in browser automation. Ideal for:
- **External services** - Already running FlareSolverr or similar for other applications
- **Alternative sources** - Using Prowlarr, IRC, or other configured sources
- **Audiobooks** - Using Shelfmark primarily for audiobooks
- **Constrained hosts** - No bundled browser, so it runs comfortably below the standard image's [memory requirements](#memory-requirements)
```bash
curl -O https://raw.githubusercontent.com/calibrain/shelfmark/main/compose/docker-compose.lite.yml
@@ -182,6 +248,8 @@ volumes:
With any authentication method enabled, Shelfmark supports multi-user management with admin/user roles. Users can have per-user settings for download destinations, email recipients, and notification preferences. Non-admin users only see their own downloads and can submit book requests for admin review. Admins can configure request policies per source to control whether users can download directly, must submit a request, or are blocked entirely.
See [API Access](docs/api-access.md) to call the API with a static key from scripts and integrations.
## Project Scope
Shelfmark is a manual search and download tool, the entry point to your book library, not a library manager. It finds books, downloads them, and sends them to a configured destination. That's the full scope.
@@ -197,9 +265,11 @@ These are non-goals, not missing features.
## Contributing
Shelfmark's core feature set is complete. Development focuses on stability, bug fixes, quality-of-life improvements, and refining the search experience. Contributions in these areas are welcome, please file issues or submit pull requests on GitHub.
Shelfmark's core feature set is complete.
Feature requests that fall outside the project scope (library integration, automation, collection management) will be closed. If you're unsure whether something fits, open a discussion first.
Pull requests are welcome and all of them get reviewed, new features included. If you want a feature, the fastest path is to send a PR for it rather than to file a request.
Feature requests that fall outside the project scope (library integration, automation, collection management) will be closed, and PRs implementing them won't be merged. If you're unsure whether something fits, open a discussion first.
## Health Monitoring
@@ -219,7 +289,10 @@ Logs are available via:
- `docker logs <container-name>`
- `/var/log/shelfmark/` inside the container (when `ENABLE_LOGGING=true`)
Log level is configurable via Settings or `LOG_LEVEL` environment variable.
Log level is configurable under Settings → Advanced or via the `LOG_LEVEL` environment
variable (`DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`; case-insensitive, defaults to
`INFO`). The environment variable wins over the setting, and `DEBUG=true` forces `DEBUG`
regardless of either. Changes take effect on restart.
## Development
+117
View File
@@ -184,6 +184,12 @@ def _generate_bootstrap_env_docs() -> list[str]:
"type": "boolean",
"default": "false",
},
{
"name": "SHELFMARK_API_KEY",
"description": "Optional static API key. When set, requests carrying it as 'Authorization: Bearer <key>' (or X-Api-Key) are authenticated as an admin; browser sessions keep working. Unset = off.",
"type": "string",
"default": "unset",
},
{
"name": "OIDC_AUTO_REDIRECT",
"description": "Automatically redirect to the OIDC provider instead of showing the login page.",
@@ -238,6 +244,113 @@ def _generate_bootstrap_env_docs() -> list[str]:
return lines
def _generate_egress_env_docs() -> list[str]:
"""Generate documentation for VPN/Tor egress environment variables.
These are startup-only variables consumed by entrypoint.sh / wireguard.sh
(before and outside the settings registry) to select and configure the
transparent-egress kill-switch. `USING_TOR` has a registry-backed entry
under Network and is cross-referenced rather than repeated here so the two
mutually exclusive egress modes are discoverable side by side without
emitting a duplicate `#### USING_TOR` anchor.
"""
egress_vars = [
{
"name": "USING_WIREGUARD",
"description": "Route all traffic through a WireGuard VPN tunnel with a fail-closed iptables kill-switch (non-tunnel egress is dropped). Requires root startup and NET_ADMIN (plus NET_RAW). Mutually exclusive with USING_TOR.",
"type": "boolean",
"default": "false",
},
{
"name": "WIREGUARD_CONFIG",
"description": "Path to the mounted wg-quick configuration file.",
"type": "string (path)",
"default": "/config/wg0.conf",
},
{
"name": "WIREGUARD_INTERFACE",
"description": "WireGuard interface name brought up by wg-quick.",
"type": "string",
"default": "wg0",
},
{
"name": "LAN_NETWORK",
"description": "Comma-separated CIDRs kept off the tunnel so the WebUI and internal download clients (Prowlarr, qBittorrent) stay reachable.",
"type": "string (comma-separated)",
"default": "127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16",
},
{
"name": "WIREGUARD_ENFORCE_DNS",
"description": "Pin the container's resolver so DNS cannot silently fall back to an off-tunnel path. The resolver used is WIREGUARD_DNS if set, else the tunnel config's DNS = line. This does NOT force queries through the tunnel: it is designed for a trusted LAN resolver kept reachable off-tunnel via LAN_NETWORK (the query leaves over the LAN; the resolver encrypts upstream while the download still egresses via the tunnel). Special case: when Docker's embedded resolver (nameserver 127.0.0.11) is present, it is PRESERVED so container-name resolution (e.g. prowlarr, qbittorrent) keeps working, and the embedded resolver's upstream must be pinned via the container's compose dns: list. Fails closed (refuses to start) only when no embedded resolver is present AND no resolver is defined, or /etc/resolv.conf is not writable.",
"type": "boolean",
"default": "true",
},
{
"name": "WIREGUARD_DNS",
"description": "Explicit resolver(s) (comma/space separated) to pin when WIREGUARD_ENFORCE_DNS is true and Docker's embedded resolver is NOT in use. Use when the VPN's pushed DNS filters domains you need; point it at a resolver reachable via the tunnel or an allowed LAN resolver. NOTE: when the embedded resolver (127.0.0.11) is present it is preserved and this value cannot repoint its upstream from inside the container — set the container's compose dns: list to the trusted resolver instead.",
"type": "string (comma-separated)",
"default": "unset (uses config DNS = line)",
},
{
"name": "WIREGUARD_DISABLE_IPV6",
"description": "Strip IPv6 Address/AllowedIPs/DNS from the tunnel config before wg-quick (many container kernels lack the ip6tables raw table wg-quick needs) and remove IPv6 as a leak surface.",
"type": "boolean",
"default": "true",
},
{
"name": "WIREGUARD_ALLOW_IPV6_LEAK",
"description": "Escape hatch: continue startup even when an IPv6 kill-switch cannot be installed AND IPv6 cannot be disabled. Only set when the container has no IPv6 connectivity, as IPv6 egress may otherwise bypass the tunnel.",
"type": "boolean",
"default": "false",
},
{
"name": "WIREGUARD_ALLOW_WEBUI_OFFTUNNEL",
"description": "When false (default) the kill-switch is strictly fail-closed: the only off-tunnel egress permitted is loopback, the tunnel device and the LAN allowlist. Set true only if a NON-LAN client (e.g. a public reverse proxy on a different segment) must reach the WebUI; it permits app-server REPLY packets (--sport FLASK_PORT, conntrack REPLY) to leave off-tunnel. Server replies only, never client-initiated egress, so it cannot leak outbound browsing/downloads or the real IP for outbound requests, but it is still an off-tunnel path while the tunnel is down, hence opt-in. LAN WebUI clients never need it (covered by LAN_NETWORK).",
"type": "boolean",
"default": "false",
},
{
"name": "WIREGUARD_STALE_AFTER",
"description": "Seconds since the last WireGuard handshake before the healthcheck bounces the tunnel.",
"type": "number",
"default": "180",
},
]
lines = [
"## Egress / VPN Routing",
"",
"These startup-only variables are consumed by `entrypoint.sh` / `wireguard.sh` to select and configure the WireGuard transparent-egress kill-switch. `USING_WIREGUARD` and [`USING_TOR`](#using_tor) (documented under Network) are mutually exclusive; both require root startup.",
"",
"| Variable | Description | Type | Default |",
"|----------|-------------|------|---------|",
]
lines.extend(
f"| `{var['name']}` | {var['description']} | {var['type']} | `{var['default']}` |"
for var in egress_vars
)
lines.append("")
lines.append("<details>")
lines.append("<summary>Detailed descriptions</summary>")
lines.append("")
for var in egress_vars:
lines.append(f"#### `{var['name']}`")
lines.append("")
lines.append(var["description"])
lines.append("")
lines.append(f"- **Type:** {var['type']}")
lines.append(f"- **Default:** `{var['default']}`")
lines.append("")
lines.append("</details>")
lines.append("")
return lines
def generate_env_docs() -> str:
"""Generate markdown documentation for all environment variables."""
# Import settings modules to ensure all settings are registered
@@ -282,6 +395,7 @@ def generate_env_docs() -> str:
# Generate TOC
toc_entries = [
"- [Bootstrap Configuration](#bootstrap-configuration)",
"- [Egress / VPN Routing](#egress--vpn-routing)",
]
# Ungrouped tabs first
@@ -307,6 +421,9 @@ def generate_env_docs() -> str:
# Add bootstrap environment variables documentation
lines.extend(_generate_bootstrap_env_docs())
# Add egress / VPN routing (startup-only, shell-driven) documentation
lines.extend(_generate_egress_env_docs())
# Generate documentation for ungrouped tabs
for tab in grouped_tabs.get(None, []):
lines.extend(_generate_tab_docs(tab))
+11
View File
@@ -3,3 +3,14 @@
class BypassCancelledError(Exception):
"""Raised when a bypass operation is cancelled."""
class ChallengeNotSolvedError(Exception):
"""Raised when a bypasser ran but the site still answered with a challenge.
Distinct from a bypasser that is broken or unreachable, which is what every
"the bypass failed" message used to say. A solver can do its job perfectly and
still be handed something it cannot clear - DDoS-Guard's manual CAPTCHA page is
the case from #1292 - and telling the user to go check that FlareSolverr is
reachable sends them to fix a service that is working.
"""
+52
View File
@@ -0,0 +1,52 @@
"""Challenge-page detection shared by the bypassers and the HTTP retry path.
Kept out of `internal_bypasser` so the HTTP layer can recognise an interstitial
without importing SeleniumBase: that module is imported lazily precisely because its
browser dependencies are optional, and external-bypasser setups run without them.
"""
# Matched against lowercased text, so every entry must be lowercase.
CLOUDFLARE_INDICATORS = [
"just a moment",
"verify you are human",
"verifying you are human",
"cloudflare.com/products/turnstile",
]
DDOS_GUARD_INDICATORS = [
"ddos-guard",
"ddos guard",
"checking your browser before accessing",
"complete the manual check to continue",
"could not verify your browser automatically",
]
# Markers that exist only in raw markup: the bypassers scan rendered innerText, where
# a script src or a <title> never appears. The title match is scoped to the tag on
# purpose - hosts word the rest of that sentence differently, and matching "checking
# your browser" as free text would trip on any page that merely discusses a challenge.
_RAW_HTML_MARKERS = (
"<title>checking your browser",
"/cdn-cgi/challenge-platform",
"/.well-known/ddos-guard/",
)
# An interstitial is a few KB of markup. Past that it is a real page that happens to
# mention a marker - a protected site links its own DDoS-Guard endpoints on every page.
MAX_CHALLENGE_HTML_CHARS = 64 * 1024
def challenge_marker(html: str) -> str | None:
"""Return the marker proving `html` is an unsolved challenge page, or None.
Only meaningful for a response that already carries a challenge status: the
markers appear on protected sites' real pages too, so the status is what
separates "blocked" from "served".
"""
if not html or len(html) > MAX_CHALLENGE_HTML_CHARS:
return None
lowered = html.lower()
for marker in (*_RAW_HTML_MARKERS, *DDOS_GUARD_INDICATORS, *CLOUDFLARE_INDICATORS):
if marker in lowered:
return marker
return None
+286
View File
@@ -0,0 +1,286 @@
"""Clearance cookies won by a bypass, shared by every bypasser implementation.
Kept in its own module rather than inside a bypasser because both of them feed it and
both read from it. The internal bypasser cannot host it: it imports seleniumbase at
module scope, which is exactly the dependency an external-bypasser deployment is
entitled not to have installed.
"""
import threading
import time
from collections.abc import Mapping
from typing import Any
from urllib.parse import urlparse
from shelfmark.core.logger import setup_logger
logger = setup_logger(__name__)
# Cookie storage - shared with requests library for Cloudflare bypass
# Nested mapping of domain to cookie name to cookie metadata.
_cf_cookies: dict[str, dict] = {}
_cf_cookies_lock = threading.Lock()
# User-Agent storage - Cloudflare ties cf_clearance to the UA that solved the challenge
_cf_user_agents: dict[str, str] = {}
# Protection cookie names we care about (Cloudflare and DDoS-Guard)
CF_COOKIE_NAMES = {"cf_clearance", "__cf_bm", "cf_chl_2", "cf_chl_prog"}
DDG_COOKIE_NAMES = {
"__ddg1_",
"__ddg2_",
"__ddg5_",
"__ddg8_",
"__ddg9_",
"__ddg10_",
"__ddgid_",
"__ddgmark_",
"ddg_last_challenge",
}
# Anna's Archive's own pass for its ?check=1 hop. Without it the hop 302s back
# forever, however good the __ddg* clearance is.
AA_COOKIE_NAMES = {"aa_ddg_check"}
# DDoS-Guard cookies that describe *one* check rather than granting clearance, and so
# must never be replayed on a later request. Observed live on Anna's Archive:
#
# __ddg9_ the client IP address
# __ddg10_ the unix timestamp the check was issued
# __ddg8_ an opaque token issued with them, same ~40 minute expiry
#
# Clearance itself lives in __ddg1_/__ddg2_/__ddgid_ (roughly a year) and __ddg5_.
# Replaying the trio is actively harmful: once the timestamp ages out - or the egress
# IP changes, which happens routinely behind a VPN - the values no longer describe the
# caller, DDoS-Guard re-arms its check and answers every request with a ?check=1
# redirect. That is the redirect loop, and it is self-inflicted. Dropping them simply
# lets DDoS-Guard issue a fresh set, exactly as it does for a browser.
DDG_EPHEMERAL_COOKIE_NAMES = {
"__ddg8_",
"__ddg9_",
"__ddg10_",
"ddg_last_challenge",
}
def _get_base_domain(domain: str) -> str:
"""Extract base domain from hostname (e.g., 'www.example.com' -> 'example.com')."""
return ".".join(domain.split(".")[-2:]) if "." in domain else domain
def _get_full_cookie_domains() -> set[str]:
"""Return mirror domains that need full-session cookie extraction."""
from shelfmark.core.mirrors import get_zlib_cookie_domains
return {_get_base_domain(domain) for domain in get_zlib_cookie_domains()}
def _replay_per_check_cookies() -> bool:
"""Whether the per-check trio is kept rather than dropped (see env.py)."""
from shelfmark.config import env
return env.DDG_REPLAY_PER_CHECK_COOKIES
def _should_extract_cookie(name: str, *, extract_all: bool) -> bool:
"""Determine if a cookie should be extracted based on its name."""
# Checked before extract_all: a per-check token is wrong to replay for every
# domain, including the full-session ones.
if name in DDG_EPHEMERAL_COOKIE_NAMES and not _replay_per_check_cookies():
return False
if extract_all:
return True
is_cf = name in CF_COOKIE_NAMES or name.startswith("cf_")
is_ddg = name in DDG_COOKIE_NAMES or name.startswith("__ddg")
is_aa = name in AA_COOKIE_NAMES
return is_cf or is_ddg or is_aa
def _cookie_field(cookie: Any, name: str) -> Any:
"""Read one field from a cookie in either shape we are handed.
The internal bypasser extracts CDP cookie objects; an external bypasser returns
the same fields as JSON objects, so the difference is attribute versus key access.
"""
if isinstance(cookie, Mapping):
return cookie.get(name)
return getattr(cookie, name, None)
def _cookie_expiry(cookie: Any) -> float | None:
"""A cookie's absolute expiry, or None when it is a session cookie.
The two spellings are not interchangeable and both reach this store. CDP and
Playwright cookies carry `expires`; the WebDriver cookie object - what a
Selenium-based solver such as FlareSolverr returns - carries `expiry`. Reading
only one silently turns every cookie from the other into a never-expiring one,
which is exactly how dead clearance ends up replayed forever (see
get_cf_cookies_for_domain).
The value is coerced rather than trusted: it arrives as JSON from a service we
do not control, and a string here used to raise straight out of the store.
"""
for field in ("expires", "expiry"):
raw = _cookie_field(cookie, field)
if raw is None:
continue
try:
expiry = float(raw)
except TypeError, ValueError:
logger.debug("Unreadable cookie expiry %r; treating as a session cookie", raw)
return None
# <= 0 is how both shapes spell "session cookie", not "expired in 1970".
return expiry if expiry > 0 else None
return None
def store_extracted_cookies(
*,
url: str,
cookies: list[Any],
user_agent: str | None = None,
) -> None:
"""Store filtered bypass cookies (and optional UA) for a URL domain."""
parsed = urlparse(url)
domain = parsed.hostname or ""
if not domain:
return
base_domain = _get_base_domain(domain)
extract_all = base_domain in _get_full_cookie_domains()
cookies_found: dict[str, dict[str, Any]] = {}
dropped: list[str] = []
for cookie in cookies:
name = _cookie_field(cookie, "name") or ""
if not _should_extract_cookie(name, extract_all=extract_all):
dropped.append(name)
continue
secure = _cookie_field(cookie, "secure")
cookies_found[name] = {
"value": _cookie_field(cookie, "value") or "",
"domain": _cookie_field(cookie, "domain") or domain,
"path": _cookie_field(cookie, "path") or "/",
"expiry": _cookie_expiry(cookie),
"secure": True if secure is None else bool(secure),
"httpOnly": True,
}
# Names only, never values. Which cookies a solve won, and which of them were held
# back, is the evidence needed to settle what DDoS-Guard actually treats as clearance
# (issue #1276) - and without it a debug log shows a solve succeeding and the next
# request being challenged with nothing in between to explain why.
logger.debug(
"Solve on %s won %s; keeping %s; dropping %s",
base_domain,
sorted({_cookie_field(c, "name") or "" for c in cookies}),
sorted(cookies_found),
sorted(set(dropped)) or "nothing",
)
if not cookies_found:
return
with _cf_cookies_lock:
_cf_cookies[base_domain] = cookies_found
if user_agent:
_cf_user_agents[base_domain] = user_agent
logger.debug("Stored UA for %s: %s...", base_domain, str(user_agent)[:60])
else:
logger.debug("No UA captured for %s", base_domain)
cookie_type = "all" if extract_all else "protection"
logger.debug("Extracted %s %s cookies for %s", len(cookies_found), cookie_type, base_domain)
def _is_cookie_expired(cookie: dict[str, Any]) -> bool:
"""Whether a stored cookie's expiry has passed. Session cookies never expire here."""
expiry = cookie.get("expiry")
if expiry is None:
expiry = cookie.get("expires")
if not expiry or expiry <= 0:
return False
return time.time() > expiry
def get_cf_cookies_for_domain(domain: str) -> dict[str, str]:
"""Get stored cookies for a domain. Returns empty dict if none available."""
if not domain:
return {}
base_domain = _get_base_domain(domain)
with _cf_cookies_lock:
cookies = _cf_cookies.get(base_domain, {})
if not cookies:
return {}
cf_clearance = cookies.get("cf_clearance", {})
if cf_clearance and _is_cookie_expired(cf_clearance):
logger.debug("CF cookies expired for %s", base_domain)
_cf_cookies.pop(base_domain, None)
return {}
# Expiry applies to every cookie, not just Cloudflare's. DDoS-Guard domains
# have no cf_clearance, so the check above never fired for them and dead
# cookies were replayed indefinitely - the server answers those with a
# challenge, which is indistinguishable from having sent nothing at all.
live = {name: c for name, c in cookies.items() if not _is_cookie_expired(c)}
if len(live) != len(cookies):
expired = sorted(set(cookies) - set(live))
logger.debug("Dropping expired cookies for %s: %s", base_domain, expired)
if live:
_cf_cookies[base_domain] = live
else:
_cf_cookies.pop(base_domain, None)
return {name: c["value"] for name, c in live.items()}
def has_valid_cf_cookies(domain: str) -> bool:
"""Check if we have valid Cloudflare cookies for a domain."""
return bool(get_cf_cookies_for_domain(domain))
def get_cf_user_agent_for_domain(domain: str) -> str | None:
"""Get the User-Agent that was used during bypass for a domain."""
if not domain:
return None
with _cf_cookies_lock:
return _cf_user_agents.get(_get_base_domain(domain))
def export_store() -> tuple[dict[str, dict], dict[str, str]]:
"""Snapshot the whole store, for handing to another process.
The internal bypasser's Docker helper solves in a subprocess, so the clearance it
wins has to be serialized back to the parent or the solve is lost with the child.
"""
with _cf_cookies_lock:
return (
{domain: dict(cookies) for domain, cookies in _cf_cookies.items()},
dict(_cf_user_agents),
)
def import_store(cookies: object, user_agents: object) -> None:
"""Merge a snapshot produced by :func:`export_store` into this process's store."""
with _cf_cookies_lock:
if isinstance(cookies, dict):
_cf_cookies.update(cookies)
if isinstance(user_agents, dict):
_cf_user_agents.update(
{str(domain): str(agent) for domain, agent in user_agents.items()}
)
def clear_cf_cookies(domain: str | None = None) -> None:
"""Clear stored Cloudflare cookies and User-Agent. If domain is None, clear all."""
with _cf_cookies_lock:
if domain:
base_domain = _get_base_domain(domain)
_cf_cookies.pop(base_domain, None)
_cf_user_agents.pop(base_domain, None)
else:
_cf_cookies.clear()
_cf_user_agents.clear()
+114 -5
View File
@@ -2,17 +2,20 @@
import random
import time
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any
import requests
from shelfmark.bypass import BypassCancelledError
from shelfmark.bypass import BypassCancelledError, ChallengeNotSolvedError
from shelfmark.bypass.challenge import challenge_marker
from shelfmark.bypass.cookie_store import store_extracted_cookies
from shelfmark.core.config import config
from shelfmark.core.logger import setup_logger
from shelfmark.core.utils import normalize_http_url
from shelfmark.download.network import get_ssl_verify
if TYPE_CHECKING:
from collections.abc import Mapping
from threading import Event
from shelfmark.download import network
@@ -47,8 +50,55 @@ def _coerce_timeout_ms(value: object, default: int) -> int:
return default
def max_duration_seconds() -> float:
"""Upper bound on how long get_bypassed_page() can take for one URL.
MAX_RETRY attempts at the configured read timeout, plus the exponential backoff waited
between them (jitter is < 1s per gap, counted as a full second to stay conservative).
Callers use this to declare a stall-detection grace; see shelfmark.download.activity.
"""
bypasser_timeout = _coerce_timeout_ms(config.get("EXT_BYPASSER_TIMEOUT", 60000), 60000)
read_timeout = min((bypasser_timeout / 1000) + READ_TIMEOUT_BUFFER, MAX_READ_TIMEOUT)
backoff_total = sum(
min(BACKOFF_CAP, BACKOFF_BASE * (2 ** (attempt - 1))) + 1.0
for attempt in range(1, MAX_RETRY)
)
return MAX_RETRY * read_timeout + backoff_total
def _store_solution_clearance(target_url: str, solution: Mapping[str, Any]) -> None:
"""Keep the clearance the solver won, so later requests do not re-solve.
A solve is the expensive part of an external bypass - tens of seconds of real
browser - and FlareSolverr-compatible services hand back the cookies and the
User-Agent that earned it. Dropping them meant every single request paid a 403
plus a full solve, and a file download (which the solver cannot proxy, being
binary) never presented clearance at all.
The UA matters as much as the cookies: Cloudflare ties cf_clearance to the UA
that solved the challenge, so replaying the cookie under our own UA is rejected.
"""
cookies = solution.get("cookies") or []
if not isinstance(cookies, list):
logger.debug("External bypasser returned no usable cookie list for '%s'", target_url)
return
user_agent = solution.get("userAgent")
store_extracted_cookies(
url=target_url,
cookies=cookies,
user_agent=user_agent if isinstance(user_agent, str) else None,
)
def _fetch_via_bypasser(target_url: str) -> str | None:
"""Make a single request to the external bypasser service. Returns HTML or None."""
"""Make a single request to the external bypasser service. Returns HTML or None.
Raises:
ChallengeNotSolvedError: the service answered with a page that is still a
challenge, whatever verdict it reported on itself.
"""
raw_bypasser_url = _coerce_config_str(
config.get("EXT_BYPASSER_URL", "http://flaresolverr:8191"),
"http://flaresolverr:8191",
@@ -100,6 +150,41 @@ def _fetch_via_bypasser(target_url: str) -> str | None:
logger.warning("External bypasser returned empty response for '%s'", target_url)
return None
# "Challenge solved!" is the solver's verdict on its own work, and #1289 showed
# it can be reported alongside a page the caller then rejects. Say what actually
# came back, so a later report does not have to infer it from downstream errors.
marker = challenge_marker(html)
logger.debug(
"External bypasser page for '%s': %d bytes, challenge_marker=%r",
target_url,
len(html),
marker,
)
if marker:
# The solver's verdict is not evidence; the page is. Returning this one as a
# success is what made #1292 unrecoverable: the retry-and-rotate loop that
# could still have saved the search - the next mirror is a different
# DDoS-Guard host, in its own state - was never entered, and the challenge
# page's own __ddg cookies were filed as this host's clearance and replayed
# on every later request.
logger.warning(
"External bypasser reported success but returned a challenge page for "
"'%s' (%d bytes, marker=%r) - the solve did not clear the protection",
target_url,
len(html),
marker,
)
raise ChallengeNotSolvedError(marker)
try:
_store_solution_clearance(target_url, solution)
except AttributeError, KeyError, TypeError, ValueError:
# Storing clearance is an optimisation; the page is the product. The
# solution JSON comes from a service we do not control, so a surprise in
# its cookie shape must not discard HTML that already cost a ~30s solve
# and send the caller round for up to MAX_RETRY more of them.
logger.debug("Could not store bypass clearance for '%s'", target_url, exc_info=True)
except requests.exceptions.Timeout:
logger.warning(
"External bypasser timed out for '%s' (connect: %ss, read: %.0fs)",
@@ -140,16 +225,33 @@ def get_bypassed_page(
selector: network.AAMirrorSelector | None = None,
cancel_flag: Event | None = None,
) -> str | None:
"""Fetch HTML via external bypasser with retries and mirror rotation."""
"""Fetch HTML via external bypasser with retries and mirror rotation.
Raises:
ChallengeNotSolvedError: every attempt came back still carrying a challenge.
Reported apart from returning None because the two ask the user for
opposite things: None means go and check the bypasser, this means the
bypasser is fine and the host is the one refusing.
BypassCancelledError: the caller's cancel flag was set.
"""
from shelfmark.download import network as network_module
sel = selector or network_module.AAMirrorSelector()
unsolved_marker: str | None = None
for attempt in range(1, MAX_RETRY + 1):
_check_cancelled(cancel_flag, "by user")
attempt_url = sel.rewrite(url)
result = _fetch_via_bypasser(attempt_url)
try:
result = _fetch_via_bypasser(attempt_url)
except ChallengeNotSolvedError as e:
# Worth the remaining attempts rather than an immediate give-up: the retry
# rotates onto the next mirror, and that is a different DDoS-Guard host with
# its own idea of whether this caller needs a CAPTCHA.
unsolved_marker = str(e) or unsolved_marker
result = None
if result:
return result
@@ -170,4 +272,11 @@ def get_bypassed_page(
if action in ("mirror", "dns") and new_base:
logger.info("Rotated %s for retry", action)
if unsolved_marker:
msg = (
"The bypasser ran, but the site kept answering with a protection challenge "
f"(marker={unsolved_marker!r}). That is usually a manual CAPTCHA, which no "
"bypasser can answer - the bypasser itself is working. Try again shortly."
)
raise ChallengeNotSolvedError(msg)
return None
File diff suppressed because it is too large Load Diff
+16
View File
@@ -0,0 +1,16 @@
"""Recognize Anna's Archive pages that require a live JavaScript timer."""
from urllib.parse import urlparse
from bs4 import BeautifulSoup
class WaitingRoomTimeoutError(TimeoutError):
"""The source waiting room did not finish within the browser session budget."""
def is_aa_waiting_room(url: str, html: str) -> bool:
"""Match the download route and actual timer element, not a script reference."""
return urlparse(url).path.startswith("/slow_download/") and bool(
BeautifulSoup(html, "html.parser").select_one(".js-partner-countdown")
)
+2 -1
View File
@@ -2,6 +2,7 @@
from __future__ import annotations
import hashlib
from typing import Any
from shelfmark.core.config import config
@@ -23,7 +24,7 @@ _BOOKLORE_OPTIONS_CACHE: dict[str, Any] = {
def _get_booklore_cache_key(base_url: str, username: str, password: str) -> str:
return f"{base_url}|{username}|{hash(password)}"
return f"{base_url}|{username}|{hashlib.sha256(password.encode()).hexdigest()}"
def _get_booklore_select_options(
+75 -13
View File
@@ -6,34 +6,78 @@ import shutil
import tempfile
from pathlib import Path
LOG_LEVELS = ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL")
def string_to_bool(s: str) -> bool:
"""Convert string to boolean."""
return s.lower() in ["true", "yes", "1", "y"]
def _read_advanced_config(key: str) -> object | None:
"""Read a key from the advanced settings file (import-time safe)."""
config_dir = Path(os.getenv("CONFIG_DIR", "/config"))
config_file = config_dir / "plugins" / "advanced.json"
if config_file.exists():
try:
with config_file.open() as f:
config = json.load(f)
if key in config:
return config[key]
except json.JSONDecodeError, OSError:
pass
return None
def _read_debug_from_config() -> bool:
"""Read DEBUG from env var or config file (import-time safe)."""
env_debug = os.environ.get("DEBUG")
if env_debug is not None:
return string_to_bool(env_debug)
# Try to read from config file
config_dir = Path(os.getenv("CONFIG_DIR", "/config"))
config_file = config_dir / "plugins" / "advanced.json"
if config_file.exists():
try:
with config_file.open() as f:
config = json.load(f)
if "DEBUG" in config:
return bool(config["DEBUG"])
except json.JSONDecodeError, OSError:
pass
value = _read_advanced_config("DEBUG")
if value is not None:
return bool(value)
return False
def normalize_log_level(raw: str | None) -> str:
"""Normalize a log level name, falling back to INFO when unrecognized."""
if raw is None:
return "INFO"
normalized = raw.strip().upper()
# "WARN" is a logging alias, but gunicorn only accepts "warning".
if normalized == "WARN":
normalized = "WARNING"
if normalized not in LOG_LEVELS:
return "INFO"
return normalized
def _read_log_level_from_config(debug: bool) -> str:
"""Resolve the app log level from DEBUG, env var, or config file.
DEBUG wins when enabled, mirroring how entrypoint.sh picks gunicorn's level.
Otherwise LOG_LEVEL is read from the env var, then the settings file, and
falls back to INFO when unset or unrecognized.
"""
if debug:
return "DEBUG"
raw = os.environ.get("LOG_LEVEL")
if raw is None:
value = _read_advanced_config("LOG_LEVEL")
raw = value if isinstance(value, str) else None
return normalize_log_level(raw)
def _is_sqlite_file(path: Path) -> bool:
"""Check if a file is a valid SQLite database by reading magic bytes."""
try:
@@ -101,7 +145,7 @@ INGEST_DIR = Path(os.getenv("INGEST_DIR", "/books"))
# =============================================================================
DEBUG = _read_debug_from_config()
LOG_LEVEL = "DEBUG" if DEBUG else "INFO"
LOG_LEVEL = _read_log_level_from_config(DEBUG)
ENABLE_LOGGING = string_to_bool(os.getenv("ENABLE_LOGGING", "true"))
@@ -122,6 +166,9 @@ SESSION_COOKIE_NAME = "shelfmark_session"
CWA_DB_PATH = _resolve_cwa_db_path()
HIDE_LOCAL_AUTH = string_to_bool(os.getenv("HIDE_LOCAL_AUTH", "false"))
DISABLE_LOCAL_AUTH = string_to_bool(os.getenv("DISABLE_LOCAL_AUTH", "false"))
# Optional static API key. When set, requests carrying it as a Bearer token
# (or X-Api-Key) are authenticated as an admin for that request only.
SHELFMARK_API_KEY = os.getenv("SHELFMARK_API_KEY", "").strip()
OIDC_AUTO_REDIRECT = string_to_bool(os.getenv("OIDC_AUTO_REDIRECT", "false"))
@@ -159,6 +206,21 @@ ONBOARDING = string_to_bool(os.getenv("ONBOARDING", "true"))
_DEBUG_SKIP_SOURCES_RAW = os.getenv("DEBUG_SKIP_SOURCES", "").strip().lower()
DEBUG_SKIP_SOURCES = {s.strip() for s in _DEBUG_SKIP_SOURCES_RAW.split(",") if s.strip()}
# Debug: keep DDoS-Guard's __ddg8_/__ddg9_/__ddg10_ in the clearance store instead of
# dropping them after a solve.
#
# Which of DDoS-Guard's cookies actually *are* clearance is not settled. The store treats
# the trio as describing one check (client IP, timestamp, token) and drops them, on the
# reasoning that replaying a stale IP/timestamp is what re-arms the ?check=1 loop - see
# shelfmark.bypass.cookie_store. Field reports on issue #1276 point the other way: every
# request after a successful solve was challenged again, which is only consistent with
# what the store keeps not being sufficient clearance on its own.
#
# Deliberately env-only and off by default: this is a knob for reproducing the question
# against a live host, not a setting to offer users. Set it to true, solve once, and watch
# whether the next search still logs "Redirect loop detected".
DDG_REPLAY_PER_CHECK_COOKIES = string_to_bool(os.getenv("DDG_REPLAY_PER_CHECK_COOKIES", "false"))
# =============================================================================
# Legacy migration support - will be removed in future version
+56 -1
View File
@@ -7,7 +7,7 @@ from pathlib import Path
from typing import TYPE_CHECKING, Any, Protocol
if TYPE_CHECKING:
from collections.abc import Callable
from collections.abc import Callable, Sequence
from os import PathLike
_DEPRECATED_SETTINGS_RESTRICTION_KEYS = (
@@ -16,6 +16,13 @@ _DEPRECATED_SETTINGS_RESTRICTION_KEYS = (
"RESTRICT_SETTINGS_TO_ADMIN",
)
# The audiobook format list shipped as the default until the format sets were unified.
# It only covered m4b/mp3, so FLAC/OPUS/OGG/M4A releases were dropped from search results
# and rejected after download - and the wider default alone would never reach existing
# installs, because initialize_default_configs() only writes defaults when the config
# file does not exist yet.
_LEGACY_AUDIOBOOK_FORMATS_DEFAULT = ("m4b", "mp3")
class MigrationLogger(Protocol):
"""Logger surface used by config migration helpers."""
@@ -57,6 +64,54 @@ def _pick_legacy_settings_restriction(config: dict[str, Any]) -> bool | None:
return None
def migrate_audiobook_formats(
*,
load_general_config: Callable[[], dict[str, Any]],
# `object` rather than `None`: the result is discarded, and savers that report
# success (settings_registry.save_config_file returns bool) are not assignable to a
# `-> None` callable.
save_general_config: Callable[[dict[str, Any]], object],
widened_formats: Sequence[str],
logger: MigrationLogger,
) -> None:
"""Widen an untouched audiobook format list to the current, fuller default.
Only a list that still matches the old default exactly is rewritten. Any other value
means someone chose it deliberately, and a migration that "helpfully" re-enabled
formats a user had turned off would be worse than leaving them on the narrow list.
"""
try:
config = load_general_config()
if "SUPPORTED_AUDIOBOOK_FORMATS" not in config:
# Nothing persisted, so the field default already applies.
logger.debug("No persisted audiobook formats - the current default applies")
return
current = config.get("SUPPORTED_AUDIOBOOK_FORMATS")
if not isinstance(current, list):
return
normalized = {str(fmt).strip().lower() for fmt in current if str(fmt).strip()}
if normalized != set(_LEGACY_AUDIOBOOK_FORMATS_DEFAULT):
logger.debug(
"Audiobook formats were customized (%s) - left unchanged", sorted(normalized)
)
return
save_general_config({"SUPPORTED_AUDIOBOOK_FORMATS": list(widened_formats)})
logger.info(
"Widened audiobook formats from the legacy default %s to %s",
list(_LEGACY_AUDIOBOOK_FORMATS_DEFAULT),
list(widened_formats),
)
except FileNotFoundError:
logger.debug("No existing general config file found - nothing to migrate")
except Exception:
logger.exception("Failed to migrate audiobook formats")
def migrate_security_settings(
*,
load_security_config: Callable[[], dict[str, Any]],
+12
View File
@@ -179,6 +179,18 @@ def security_settings() -> list[SettingsField]:
placeholder="e.g. admins",
default="",
),
_auth_field(
SelectField,
"proxy",
key="PROXY_AUTH_DEFAULT_ROLE",
label="Proxy Auth Default Role",
description="Role for users the proxy authenticates for the first time when no admin group is configured. The first account is always an admin so the instance is never left without one.",
options=[
{"value": "user", "label": "User"},
{"value": "admin", "label": "Admin"},
],
default="user",
),
]
fields.append(
+19 -1
View File
@@ -115,7 +115,7 @@ def check_oidc_connection(
response.raise_for_status()
document = response.json()
required_fields = ["issuer", "authorization_endpoint", "token_endpoint"]
required_fields = ["issuer", "authorization_endpoint", "token_endpoint", "jwks_uri"]
missing_fields = [field for field in required_fields if field not in document]
if missing_fields:
return {
@@ -123,6 +123,24 @@ def check_oidc_connection(
"message": f"Discovery document missing fields: {', '.join(missing_fields)}",
}
# Logins verify the ID token against the provider's JWKS, so an empty key
# set (e.g. an Authentik provider with no Signing Key selected) means every
# login will fail even though discovery looks healthy.
jwks_uri = str(document["jwks_uri"])
jwks_response = requests.get(jwks_uri, timeout=10, verify=get_ssl_verify(jwks_uri))
jwks_response.raise_for_status()
jwks_document = jwks_response.json()
jwks_keys = jwks_document.get("keys") if isinstance(jwks_document, dict) else None
if not jwks_keys:
return {
"success": False,
"message": (
"Discovery document is valid, but the provider returned no token "
"signing keys (empty JWKS), so logins will fail. If you use "
"Authentik, select a Signing Key in the provider settings."
),
}
return {"success": True, "message": f"Connected to {document['issuer']}"}
except Exception as exc:
logger.exception("OIDC connection test failed")
+173 -26
View File
@@ -1,6 +1,5 @@
"""Core settings registration and derived configuration values."""
import json
from pathlib import Path
from typing import Any
@@ -15,6 +14,8 @@ from shelfmark.config.download_settings_handlers import (
check_books_destination,
)
from shelfmark.config.email_settings import check_email_connection
from shelfmark.config.migrations import migrate_audiobook_formats
from shelfmark.core.languages import supported_book_languages
from shelfmark.core.logger import setup_logger
from shelfmark.core.settings_registry import (
ActionButton,
@@ -35,6 +36,10 @@ from shelfmark.core.settings_registry import (
register_on_save,
register_settings,
)
from shelfmark.core.utils import ARCHIVE_FORMATS, AUDIOBOOK_FORMATS
_DOWNLOAD_CLIENT_COMPLETED_PATH_TIMEOUT_DEFAULT = 60
_DOWNLOAD_CLIENT_COMPLETED_PATH_TIMEOUT_MAX = 3600
def _on_save_advanced(values: dict[str, Any]) -> dict[str, Any]:
@@ -43,6 +48,40 @@ def _on_save_advanced(values: dict[str, Any]) -> dict[str, Any]:
logger = setup_logger(__name__)
timeout_key = "DOWNLOAD_CLIENT_COMPLETED_PATH_TIMEOUT"
if timeout_key in values:
raw_timeout = values.get(timeout_key)
if isinstance(raw_timeout, bool):
return {
"error": True,
"message": "Completed Path Wait must be a number of seconds",
"values": values,
}
if raw_timeout is None:
return {
"error": True,
"message": "Completed Path Wait must be a number of seconds",
"values": values,
}
try:
timeout_seconds = int(raw_timeout)
except TypeError, ValueError:
return {
"error": True,
"message": "Completed Path Wait must be a number of seconds",
"values": values,
}
if timeout_seconds < 0 or timeout_seconds > _DOWNLOAD_CLIENT_COMPLETED_PATH_TIMEOUT_MAX:
return {
"error": True,
"message": (
"Completed Path Wait must be between 0 and "
f"{_DOWNLOAD_CLIENT_COMPLETED_PATH_TIMEOUT_MAX} seconds"
),
"values": values,
}
values[timeout_key] = timeout_seconds
mappings = values.get("PROWLARR_REMOTE_PATH_MAPPINGS")
if mappings is None:
return {"error": False, "values": values}
@@ -97,6 +136,20 @@ def _on_save_advanced(values: dict[str, Any]) -> dict[str, Any]:
logger = setup_logger(__name__)
def migrate_audiobook_format_settings() -> None:
"""Bring installs created before the audiobook format sets were unified up to date."""
from shelfmark.core.settings_registry import load_config_file, save_config_file
migrate_audiobook_formats(
load_general_config=lambda: load_config_file("general"),
save_general_config=lambda values: save_config_file("general", values),
widened_formats=[*AUDIOBOOK_FORMATS, *ARCHIVE_FORMATS],
logger=logger,
)
_SMTP_PORT_MAX = 65535
_EMAIL_ATTACHMENT_LIMIT_MB_MAX = 600
@@ -106,11 +159,8 @@ for key in ["CONFIG_DIR", "LOG_DIR", "TMP_DIR", "INGEST_DIR", "DEBUG", "DOCKERMO
if hasattr(env, key):
logger.debug(" %s: %s", key, getattr(env, key))
# Load supported book languages from data file
# Path is relative to the package root, not this file
_DATA_DIR = Path(__file__).resolve().parent.parent.parent / "data"
with (_DATA_DIR / "book-languages.json").open() as file:
_SUPPORTED_BOOK_LANGUAGE = json.load(file)
# Selectable book languages, without the resolution aliases clients do not need.
_SUPPORTED_BOOK_LANGUAGE = supported_book_languages()
# Directory settings
BASE_DIR = Path(__file__).resolve().parent.parent.parent
@@ -154,6 +204,7 @@ register_group(
# Direct mode sort options
_AA_SORT_OPTIONS = [
{"value": "", "label": "Most downloads"},
{"value": "relevance", "label": "Most relevant"},
{"value": "newest", "label": "Newest (publication year)"},
{"value": "oldest", "label": "Oldest (publication year)"},
@@ -181,11 +232,7 @@ _FORMAT_OPTIONS = [
]
_AUDIOBOOK_FORMAT_OPTIONS = [
{"value": "m4b", "label": "M4B"},
{"value": "mp3", "label": "MP3"},
{"value": "m4a", "label": "M4A"},
{"value": "zip", "label": "ZIP"},
{"value": "rar", "label": "RAR"},
{"value": fmt, "label": fmt.upper()} for fmt in (*AUDIOBOOK_FORMATS, *ARCHIVE_FORMATS)
]
_DOWNLOAD_TO_BROWSER_CONTENT_TYPE_OPTIONS = [
@@ -382,14 +429,7 @@ def general_settings() -> list[SettingsField]:
label="Supported Audiobook Formats",
description="Audiobook formats to include in search results. ZIP/RAR archives are extracted automatically and audiobook files are used if found.",
options=_AUDIOBOOK_FORMAT_OPTIONS,
default=["m4b", "mp3"],
),
MultiSelectField(
key="BOOK_LANGUAGE",
label="Default Book Languages",
description="Default language filter for searches.",
options=_LANGUAGE_OPTIONS,
default=["en"],
default=[*AUDIOBOOK_FORMATS, *ARCHIVE_FORMATS],
),
]
@@ -428,12 +468,23 @@ def search_mode_settings() -> list[SettingsField]:
default="universal",
user_overridable=True,
),
MultiSelectField(
key="BOOK_LANGUAGE",
label="Default Book Languages",
description=(
"Default language filter for searches. Users can override this for their "
"own account."
),
options=_LANGUAGE_OPTIONS,
default=["en"],
user_overridable=True,
),
SelectField(
key="AA_DEFAULT_SORT",
label="Default Sort Order",
description="Default sort order for search results.",
options=_AA_SORT_OPTIONS,
default="relevance",
default="",
show_when={"field": "SEARCH_MODE", "value": "direct"},
),
CheckboxField(
@@ -720,7 +771,10 @@ def _on_save_downloads(values: dict[str, Any]) -> dict[str, Any]:
}
# Audiobooks are always folder output.
if effective.get("FILE_ORGANIZATION_AUDIOBOOK", "rename") == "rename":
if effective.get("FILE_ORGANIZATION_AUDIOBOOK", "rename") in {
"rename",
"rename_and_group",
}:
template = effective.get("TEMPLATE_AUDIOBOOK_RENAME", "")
if _contains_path_separators(template):
return {
@@ -970,12 +1024,30 @@ def download_settings() -> list[SettingsField]:
"value": "folder",
},
),
TextField(
key="NAMING_WORD_SEPARATOR",
label="Word Separator",
description=(
"Replaces spaces inside naming template values (e.g. 'Conan Doyle' -> "
"'Conan.Doyle' with '.'). Applies to books and audiobooks, rename and "
"organize templates alike. Literal characters typed into a template "
"(like the '-' in '{Author} - {Title}') are left as-is. Leave empty to "
"keep spaces as-is."
),
default="",
placeholder=".",
max_length=5,
show_when={
"field": "BOOKS_OUTPUT_MODE",
"value": "folder",
},
),
# Rename mode template - filename only
_naming_template_field(
key="TEMPLATE_RENAME",
label="Naming Template",
description=(
"Variables: {Author}, {Title}, {Year}, {User}, {OriginalName} "
"Variables: {Author}, {FirstAuthor} (first of several authors), {Title}, {Year}, {Language}, {User}, {OriginalName} "
"(source filename without extension). Universal adds: {Series}, "
"{SeriesPosition}, {Subtitle}, {PrimaryTitle}. Use arbitrary prefix/suffix: "
"{Vol. SeriesPosition - } outputs 'Vol. 2 - ' when set, nothing when empty. "
@@ -994,7 +1066,7 @@ def download_settings() -> list[SettingsField]:
key="TEMPLATE_ORGANIZE",
label="Path Template",
description=(
"Use / to create folders. Variables: {Author}, {Title}, {Year}, {User}, "
"Use / to create folders. Variables: {Author}, {FirstAuthor} (first of several authors), {Title}, {Year}, {Language}, {User}, "
"{OriginalName} (source filename without extension). Universal adds: {Series}, "
"{SeriesPosition}, {Subtitle}, {PrimaryTitle}. Use arbitrary prefix/suffix: "
"{Vol. SeriesPosition - } outputs 'Vol. 2 - ' when set, nothing when empty."
@@ -1248,6 +1320,11 @@ def download_settings() -> list[SettingsField]:
"label": "Rename and Organize",
"description": "Create folders and rename files using a template. Recommended for Audiobookshelf. Do not use with ingest folders.",
},
{
"value": "rename_and_group",
"label": "Rename and Group",
"description": "Rename single-file downloads; keep multi-file downloads grouped in their source folder. Do not use with ingest folders.",
},
],
default="rename",
universal_only=True,
@@ -1257,7 +1334,7 @@ def download_settings() -> list[SettingsField]:
key="TEMPLATE_AUDIOBOOK_RENAME",
label="Naming Template",
description=(
"Variables: {Author}, {Title}, {Year}, {User}, {OriginalName} "
"Variables: {Author}, {FirstAuthor} (first of several authors), {Title}, {Year}, {Language}, {User}, {OriginalName} "
"(source filename without extension), {Series}, {SeriesPosition}, {Subtitle}, "
"{PrimaryTitle}, {PartNumber}. Use arbitrary prefix/suffix: "
"{Vol. SeriesPosition - } outputs 'Vol. 2 - ' when set, nothing when empty. "
@@ -1266,7 +1343,10 @@ def download_settings() -> list[SettingsField]:
),
default="{Author} - {Title}",
placeholder="{Author} - {Title}{ - Part }{PartNumber}",
show_when={"field": "FILE_ORGANIZATION_AUDIOBOOK", "value": "rename"},
show_when={
"field": "FILE_ORGANIZATION_AUDIOBOOK",
"value": ["rename", "rename_and_group"],
},
universal_only=True,
),
# Organize mode template - folders allowed
@@ -1274,7 +1354,7 @@ def download_settings() -> list[SettingsField]:
key="TEMPLATE_AUDIOBOOK_ORGANIZE",
label="Path Template",
description=(
"Use / to create folders. Variables: {Author}, {Title}, {Year}, {User}, "
"Use / to create folders. Variables: {Author}, {FirstAuthor} (first of several authors), {Title}, {Year}, {Language}, {User}, "
"{OriginalName} (source filename without extension), {Series}, {SeriesPosition}, "
"{Subtitle}, {PrimaryTitle}, {PartNumber}. Use arbitrary prefix/suffix: "
"{Vol. SeriesPosition - } outputs 'Vol. 2 - ' when set, nothing when empty."
@@ -1499,6 +1579,19 @@ def download_source_settings() -> list[SettingsField]:
min_value=1,
max_value=60,
),
NumberField(
key="RELEASE_SEARCH_TIMEOUT",
label="Release Search Timeout (seconds)",
description=(
"How long one release search may run before it gives up and reports why. "
"A first search on a cold start pays for a browser solve, so leave room "
"for one. If you use a reverse proxy, its read timeout should be at least "
"this high or it will cut the search off with a 504 first."
),
default=300,
min_value=30,
max_value=1800,
),
HeadingField(
key="content_type_routing_heading",
title="Content-Type Routing",
@@ -1609,6 +1702,31 @@ def cloudflare_bypass_settings() -> list[SettingsField]:
requires_restart=True,
show_when={"field": "USING_EXTERNAL_BYPASSER", "value": True},
),
NumberField(
key="BYPASS_PAGE_SOURCE_TIMEOUT",
label="Page Read Timeout (seconds)",
description=(
"How long to wait for a solved page to produce its content before the "
"bypass is retried. Raise it if solves succeed but searches still fail."
),
default=20,
min_value=1,
max_value=120,
show_when={"field": "USING_EXTERNAL_BYPASSER", "value": False},
),
NumberField(
key="BYPASS_BROWSER_IDLE_TIMEOUT",
label="Bypasser Idle Timeout (seconds)",
description=(
"How long the bypass helper process may sit unused before it is shut down. "
"Higher keeps more searches fast, lower frees memory sooner."
),
default=180,
min_value=30,
max_value=3600,
requires_restart=True,
show_when={"field": "USING_EXTERNAL_BYPASSER", "value": False},
),
]
@@ -1727,6 +1845,23 @@ def advanced_settings() -> list[SettingsField]:
default=False,
requires_restart=True,
),
SelectField(
key="LOG_LEVEL",
label="Log Level",
description=(
"Lowest severity written to the console and log file. "
"Ignored while Debug Mode is on, which forces Debug."
),
options=[
{"value": "DEBUG", "label": "Debug", "description": "Everything, very noisy."},
{"value": "INFO", "label": "Info", "description": "Normal activity (default)."},
{"value": "WARNING", "label": "Warning", "description": "Warnings and problems."},
{"value": "ERROR", "label": "Error", "description": "Failures only."},
{"value": "CRITICAL", "label": "Critical", "description": "Fatal errors only."},
],
default="INFO",
requires_restart=True,
),
NumberField(
key="MAIN_LOOP_SLEEP_TIME",
label="Queue Check Interval (seconds)",
@@ -1780,6 +1915,18 @@ def advanced_settings() -> list[SettingsField]:
title="Remote Path Mappings",
description="Map download client paths to paths inside Shelfmark. Needed when volume mounts differ between containers.",
),
NumberField(
key="DOWNLOAD_CLIENT_COMPLETED_PATH_TIMEOUT",
label="Completed Path Wait (seconds)",
description=(
"How long to wait after a torrent or usenet client reports completion "
"for the completed file path to become visible to Shelfmark. Increase "
"this for seedbox or remote-sync workflows."
),
default=_DOWNLOAD_CLIENT_COMPLETED_PATH_TIMEOUT_DEFAULT,
min_value=0,
max_value=_DOWNLOAD_CLIENT_COMPLETED_PATH_TIMEOUT_MAX,
),
TableField(
key="PROWLARR_REMOTE_PATH_MAPPINGS",
label="Path Mappings",
+35 -4
View File
@@ -7,6 +7,7 @@ that talks to /api/admin/users endpoints.
from typing import Any
from shelfmark.core.languages import normalize_language
from shelfmark.core.request_policy import (
get_source_content_type_capabilities,
parse_policy_mode,
@@ -61,7 +62,7 @@ _SELF_SETTINGS_SECTION_OPTIONS = [
{
"value": "search",
"label": "Search Preferences",
"description": "Show personal search mode and provider settings.",
"description": "Show personal search mode, language, and provider settings.",
},
{
"value": "notifications",
@@ -77,8 +78,9 @@ _SEARCH_PREFERENCE_PROVIDER_KEYS = {
"METADATA_PROVIDER_AUDIOBOOK",
"METADATA_PROVIDER_COMBINED",
}
_SEARCH_PREFERENCE_VALIDATABLE_KEYS = {
SEARCH_PREFERENCE_VALIDATABLE_KEYS = {
"SEARCH_MODE",
"BOOK_LANGUAGE",
"DEFAULT_RELEASE_SOURCE",
"DEFAULT_RELEASE_SOURCE_AUDIOBOOK",
"SHOW_COMBINED_SELECTOR",
@@ -178,14 +180,43 @@ def _get_request_policy_rule_columns() -> list[dict[str, object]]:
]
def _validate_book_languages(value: Any) -> tuple[Any, str | None]:
"""Validate a per-user default language list against the known languages.
Accepts the list the settings UI sends as well as a comma-separated string, so an
API client can spell the value the way the env var does. Blank entries are skipped
rather than rejected, which makes "" and "en," mean the same as [] and ["en"]. An
empty result is a deliberate override meaning "no default language filter", so it
is kept as-is; ``None`` clears the override further up the chain.
"""
entries = value.split(",") if isinstance(value, str) else value
if not isinstance(entries, (list, tuple)):
return value, "BOOK_LANGUAGE must be a list of language codes"
normalized: list[str] = []
for entry in entries:
if entry is None or (isinstance(entry, str) and not entry.strip()):
continue
code = normalize_language(entry)
if code is None:
return value, f"BOOK_LANGUAGE contains an unsupported language: {entry}"
if code not in normalized:
normalized.append(code)
return normalized, None
def validate_search_preference_value(key: str, value: Any) -> tuple[Any, str | None]:
"""Validate and normalize a search preference value for user overrides."""
if key not in _SEARCH_PREFERENCE_VALIDATABLE_KEYS:
if key not in SEARCH_PREFERENCE_VALIDATABLE_KEYS:
return value, None
if value is None:
return None, None
if key == "BOOK_LANGUAGE":
return _validate_book_languages(value)
normalized_value = str(value).strip()
if key == "SEARCH_MODE":
@@ -298,7 +329,7 @@ def _on_save_users(values: dict[str, object]) -> dict[str, object]:
}
values["REQUEST_POLICY_RULES"] = normalized_rules
for key in _SEARCH_PREFERENCE_VALIDATABLE_KEYS:
for key in SEARCH_PREFERENCE_VALIDATABLE_KEYS:
if key not in values:
continue
normalized_value, validation_error = validate_search_preference_value(key, values[key])
+12 -5
View File
@@ -280,6 +280,7 @@ def register_admin_routes(app: Flask, user_db: UserDB) -> None:
# Handle optional password update
password = data.get("password", "")
password_hash: str | None = None
if password:
if not capabilities["canSetPassword"]:
return jsonify(
@@ -292,7 +293,7 @@ def register_admin_routes(app: Flask, user_db: UserDB) -> None:
return jsonify(
{"error": f"Password must be at least {MIN_PASSWORD_LENGTH} characters"}
), 400
user_db.update_user(user_id, password_hash=generate_password_hash(password))
password_hash = generate_password_hash(password)
# Update user fields
user_fields = {}
@@ -358,10 +359,8 @@ def register_admin_routes(app: Flask, user_db: UserDB) -> None:
if field in user_fields and user_fields[field] == user.get(field):
user_fields.pop(field)
if user_fields:
user_db.update_user(user_id, **user_fields)
# Update per-user settings
# Validate per-user settings
validated_settings: dict[str, Any] | None = None
if "settings" in data:
if not isinstance(data["settings"], dict):
return jsonify({"error": "Settings must be an object"}), 400
@@ -375,6 +374,14 @@ def register_admin_routes(app: Flask, user_db: UserDB) -> None:
}
), 400
# Apply the writes only once the whole payload has been accepted.
if password_hash is not None:
user_fields["password_hash"] = password_hash
if user_fields:
user_db.update_user(user_id, **user_fields)
if validated_settings is not None:
user_db.set_user_settings(user_id, validated_settings)
# Ensure runtime reads see updated per-user overrides immediately.
try:
+7 -8
View File
@@ -11,7 +11,10 @@ from shelfmark.config.notifications_settings import (
is_valid_notification_url,
normalize_notification_routes,
)
from shelfmark.config.users_settings import validate_search_preference_value
from shelfmark.config.users_settings import (
SEARCH_PREFERENCE_VALIDATABLE_KEYS,
validate_search_preference_value,
)
from shelfmark.core.config import config as app_config
from shelfmark.core.request_policy import parse_policy_mode, validate_policy_rules
from shelfmark.core.settings_registry import load_config_file
@@ -91,13 +94,9 @@ def validate_user_settings(
if search_validation_error:
errors.append(search_validation_error)
continue
if key in {
"SEARCH_MODE",
"METADATA_PROVIDER",
"METADATA_PROVIDER_AUDIOBOOK",
"DEFAULT_RELEASE_SOURCE",
"DEFAULT_RELEASE_SOURCE_AUDIOBOOK",
}:
# Every key the search validator recognises keeps its normalized value;
# a hand-maintained subset here silently dropped normalization for the rest.
if key in SEARCH_PREFERENCE_VALIDATABLE_KEYS:
valid[key] = normalized_search_value
continue
+39
View File
@@ -0,0 +1,39 @@
"""Static API-key authentication backed by the SHELFMARK_API_KEY environment variable.
When ``SHELFMARK_API_KEY`` is set, a request carrying that value as a Bearer token or in
``X-Api-Key`` is treated as an admin for that request only. Both headers are
checked, since a reverse proxy in front of Shelfmark may set its own
``Authorization`` header, which would otherwise shadow an operator-supplied
``X-Api-Key``. A candidate that matches neither is ignored so that bearer
tokens forwarded by reverse proxies keep working. The key is never logged.
"""
from __future__ import annotations
import hmac
from shelfmark.config.env import SHELFMARK_API_KEY
def extract_api_key_candidates(
authorization_header: str | None, api_key_header: str | None
) -> list[str]:
"""Return the non-empty credentials a client presented, Bearer token first."""
candidates: list[str] = []
if authorization_header:
scheme, _, token = authorization_header.strip().partition(" ")
token = token.strip()
if scheme.lower() == "bearer" and token:
candidates.append(token)
if api_key_header:
token = api_key_header.strip()
if token:
candidates.append(token)
return candidates
def matches_api_key(candidate: str) -> bool:
"""Constant-time comparison against the configured key. False when unset."""
if not SHELFMARK_API_KEY or not candidate:
return False
return hmac.compare_digest(candidate.encode("utf-8"), SHELFMARK_API_KEY.encode("utf-8"))
+112
View File
@@ -0,0 +1,112 @@
"""Comparing and trimming author names for release search.
Lives in core because more than one release source needs it: Prowlarr ranks
results on author agreement (#1293), and IRC both trims the name it searches
for and ranks what comes back.
"""
import re
_AUTHOR_TOKEN_PATTERN = re.compile(r"\w+", re.UNICODE)
_AUTHOR_NOISE_TOKENS = frozenset(
{"jr", "sr", "ii", "iii", "iv", "phd", "md", "dr", "mr", "mrs", "ms", "et", "al", "and", "the"}
)
# Ordering tiers for author agreement between the requested book and what an
# indexer reported. Lower sorts first.
AUTHOR_MATCH = 0
AUTHOR_PARTIAL = 1
AUTHOR_UNKNOWN = 2
AUTHOR_MISMATCH = 3
# A mononym ("Homer") can only ever agree on one token; a longer name needs a
# given name and a surname to agree before it counts as the same person.
_AUTHOR_TOKENS_REQUIRED = 2
def _author_tokens(value: object) -> list[str]:
"""Split an author string into comparable lowercase name tokens."""
if not isinstance(value, str):
return []
tokens = [token.lower() for token in _AUTHOR_TOKEN_PATTERN.findall(value)]
return [token for token in tokens if token not in _AUTHOR_NOISE_TOKENS]
def _author_tokens_compatible(wanted: str, offered: str) -> bool:
"""Treat an abbreviated given name as the name it abbreviates."""
return wanted == offered or wanted.startswith(offered) or offered.startswith(wanted)
def author_affinity(wanted: object, offered: object) -> int:
"""Rank how far an indexer's author field is from the requested author.
Shelfmark ranks on this rather than filtering on it, so a wrong verdict only
costs a release its position in the list, never its visibility. That is what
makes the loose token comparison safe: "Tim"/"Timothy" and "T."/"Timothy"
agree, while a transliteration ("Dostoevsky"/"Dostoyevsky") is merely sorted
last instead of being hidden.
Graded, not binary, because the ways of falling short are not equally bad.
An indexer that reports no author at all must not sort below one that reports
a wrong author, so "no metadata" ranks between agreement and disagreement. And
a name that merely says *less* than the one asked for is not evidence of a
different person: "Petrie" contradicts nothing about "David Petrie", while
"Gordon Petrie" does. That gap matters most where a source is searched by
surname alone (#1331) - the filenames such a search is meant to reach are
exactly the ones filed under a bare surname, and ranking them as wrong put
them below every result that named someone else entirely.
"""
wanted_tokens = _author_tokens(wanted)
offered_tokens = _author_tokens(offered)
if not wanted_tokens or not offered_tokens:
return AUTHOR_UNKNOWN
matched = sum(
1
for wanted_token in wanted_tokens
if any(
_author_tokens_compatible(wanted_token, offered_token)
for offered_token in offered_tokens
)
)
required = min(_AUTHOR_TOKENS_REQUIRED, len(wanted_tokens))
if matched >= required:
return AUTHOR_MATCH
# Too little agreement to call it the same person, so the question is whether
# what was offered *disagrees*. A name every token of which fits the wanted
# name is an abbreviation of it; one carrying a token that fits nothing is a
# different name that happens to share a surname.
if all(
any(
_author_tokens_compatible(wanted_token, offered_token) for wanted_token in wanted_tokens
)
for offered_token in offered_tokens
):
return AUTHOR_PARTIAL
return AUTHOR_MISMATCH
def search_surname(author: object) -> str:
"""The one name token worth sending to a source that matches conjunctively.
Given names are where catalogues disagree - "David Petrie" is filed as
"D. Petrie", "Timothy" as "Tim" - so a query carrying one matches nothing on
a source that requires every term to appear. The surname is the token both
spellings share.
Keeps the author's own capitalisation, because the result is posted to a
public channel, and returns "" when no usable token is left so the caller
searches by title alone rather than by noise.
"""
if not isinstance(author, str):
return ""
tokens = [
token
for token in _AUTHOR_TOKEN_PATTERN.findall(author)
if token.lower() not in _AUTHOR_NOISE_TOKENS
]
if not tokens:
return ""
return tokens[-1]
+1
View File
@@ -39,6 +39,7 @@ def upsert_cwa_user(
email=normalized_email,
role=role,
allow_email_link=True,
sync_username=True,
collision_strategy=collision_strategy,
alias_suffix=_CWA_ALIAS_SUFFIX,
context=context,
+7 -3
View File
@@ -207,6 +207,7 @@ class DownloadHistoryService:
"content_type": row.get("content_type"),
"source": row.get("source"),
"source_display_name": row.get("source_display_name"),
"downloads": row.get("downloads"),
"status_message": row.get("status_message"),
"download_path": DownloadHistoryService._resolve_existing_download_path(
row.get("download_path")
@@ -272,6 +273,7 @@ class DownloadHistoryService:
size: str | None,
preview: str | None,
content_type: str | None,
downloads: int | None,
origin: str,
retry_payload: dict[str, Any] | None = None,
) -> None:
@@ -307,15 +309,16 @@ class DownloadHistoryService:
title, author, format, size, preview, content_type,
origin, final_status,
status_message, download_path, retry_payload,
queued_at, terminal_at
queued_at, terminal_at, downloads
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', NULL, NULL, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', NULL, NULL, ?, ?, ?, ?)
ON CONFLICT(task_id) DO UPDATE SET
final_status = 'active',
status_message = NULL,
download_path = NULL,
retry_payload = excluded.retry_payload,
terminal_at = ?
terminal_at = ?,
downloads = excluded.downloads
""",
(
normalized_task_id,
@@ -334,6 +337,7 @@ class DownloadHistoryService:
normalized_retry_payload,
recorded_at,
recorded_at,
downloads,
recorded_at,
),
)
+62 -4
View File
@@ -108,6 +108,7 @@ def _build_updates(
auth_source: str,
role: str,
sync_role: bool,
username: str | object,
email: str | None | object,
display_name: str | None | object,
subject_field: str | None,
@@ -116,6 +117,8 @@ def _build_updates(
updates: dict[str, Any] = {"auth_source": auth_source}
if sync_role:
updates["role"] = _normalize_role(role)
if username is not UNSET:
updates["username"] = _normalize_username(username)
if email is not UNSET:
updates["email"] = _normalize_email(email)
if display_name is not UNSET:
@@ -125,10 +128,17 @@ def _build_updates(
return updates
def _next_suffix_username(user_db: UserDB, base_username: str) -> str:
def _next_suffix_username(
user_db: UserDB,
base_username: str,
*,
exclude_user_id: int | None = None,
) -> str:
candidate = base_username
suffix = 1
while user_db.get_user(username=candidate):
while existing := user_db.get_user(username=candidate):
if exclude_user_id is not None and int(existing.get("id") or 0) == exclude_user_id:
return candidate
candidate = f"{base_username}_{suffix}"
suffix += 1
return candidate
@@ -149,7 +159,7 @@ def _find_existing_alias_user(
]
if not candidates:
return None
return sorted(candidates, key=lambda user: int(user.get("id") or 0))[0]
return min(candidates, key=lambda user: int(user.get("id") or 0), default=None)
def _resolve_create_username(
@@ -185,6 +195,38 @@ def _resolve_create_username(
return _next_suffix_username(user_db, alias_base), None, "username_collision_alias"
def _resolve_update_username(
user_db: UserDB,
*,
current_user: dict[str, Any],
requested_username: str,
strategy: CollisionStrategy,
alias_suffix: str,
) -> str:
current_user_id = int(current_user["id"])
existing = user_db.get_user(username=requested_username)
if existing is None or int(existing.get("id") or 0) == current_user_id:
return requested_username
if strategy == "suffix":
return _next_suffix_username(
user_db,
requested_username,
exclude_user_id=current_user_id,
)
if strategy == "alias":
return _next_suffix_username(
user_db,
f"{requested_username}{alias_suffix}",
exclude_user_id=current_user_id,
)
# `takeover` can select an existing row during creation, but once an
# identity is already matched it must never replace a different username
# owner. Preserve the matched row's current collision-free name instead.
return str(current_user["username"])
def upsert_external_user(
user_db: UserDB,
*,
@@ -197,6 +239,7 @@ def upsert_external_user(
subject: str | None = None,
allow_email_link: bool = False,
sync_role: bool = True,
sync_username: bool = False,
allow_create: bool = True,
collision_strategy: CollisionStrategy = "takeover",
alias_suffix: str | None = None,
@@ -229,10 +272,26 @@ def upsert_external_user(
subject=subject,
allow_email_link=allow_email_link,
)
resolved_alias_suffix = alias_suffix or f"__{auth_source}"
update_username: str | object = UNSET
if (
matched is not None
and sync_username
and normalize_auth_source(matched.get("auth_source"), matched.get("oidc_subject"))
== auth_source
):
update_username = _resolve_update_username(
user_db,
current_user=matched,
requested_username=normalized_username,
strategy=collision_strategy,
alias_suffix=resolved_alias_suffix,
)
updates = _build_updates(
auth_source=auth_source,
role=normalized_role,
sync_role=sync_role,
username=update_username,
email=normalized_email if email is not UNSET else UNSET,
display_name=normalized_display_name if display_name is not UNSET else UNSET,
subject_field=subject_field,
@@ -261,7 +320,6 @@ def upsert_external_user(
)
return None, "not_found"
resolved_alias_suffix = alias_suffix or f"__{auth_source}"
create_username, takeover_target, create_reason = _resolve_create_username(
user_db,
auth_source=auth_source,
+138
View File
@@ -0,0 +1,138 @@
"""Canonical language resolution shared by every release source.
Release sources report a language in whatever shape their upstream uses: a
two-letter code, an ISO 639-2 three-letter code in either the bibliographic or
terminological form, or an English name. They all need the same ISO 639-1 code
out the other side, so the aliases live in one place (``data/book-languages.json``)
and adding a language means editing one file.
"""
import json
import threading
import unicodedata
from pathlib import Path
from shelfmark.core.logger import setup_logger
logger = setup_logger(__name__)
LANGUAGE_DATA_PATH = Path(__file__).resolve().parents[1].parent / "data" / "book-languages.json"
# Values a source uses to mean "we could not tell".
LANGUAGE_PLACEHOLDERS = frozenset({"", "-", "--", "unknown", "unk", "n/a", "na", "none", "null"})
_ALIAS_TO_CODE: dict[str, str] | None = None
_CODE_TO_NAME: dict[str, str] | None = None
_LOCK = threading.Lock()
# Separators that stand in for the hyphen in a subtag. The dashes turn up in
# codes copied from web pages -- "zh‑Hant" used U+2011, which renders close
# enough to both a hyphen and an underscore to go unnoticed -- and the
# underscore is the spelling Direct Download accepted before this module existed.
_SUBTAG_SEPARATORS = dict.fromkeys(map(ord, "‐‑‒–—―−﹘﹣-_"), "-")
def _fold(value: str) -> str:
"""Casefold, strip accents, and normalize subtag separators, so 'Español'
and 'espanol', or 'zh-Hant', 'zh‑Hant' and 'zh_Hant', all match."""
decomposed = unicodedata.normalize("NFKD", value).translate(_SUBTAG_SEPARATORS)
stripped = "".join(ch for ch in decomposed if not unicodedata.combining(ch))
return " ".join(stripped.split()).casefold()
def _load() -> tuple[dict[str, str], dict[str, str]]:
global _ALIAS_TO_CODE, _CODE_TO_NAME
if _ALIAS_TO_CODE is not None and _CODE_TO_NAME is not None:
return _ALIAS_TO_CODE, _CODE_TO_NAME
with _LOCK:
if _ALIAS_TO_CODE is not None and _CODE_TO_NAME is not None:
return _ALIAS_TO_CODE, _CODE_TO_NAME
alias_to_code: dict[str, str] = {}
code_to_name: dict[str, str] = {}
try:
raw = json.loads(LANGUAGE_DATA_PATH.read_text(encoding="utf-8"))
except OSError, ValueError:
logger.exception("Failed to load language data from %s", LANGUAGE_DATA_PATH)
raw = []
if not isinstance(raw, list):
logger.warning("Language data at %s is not a list", LANGUAGE_DATA_PATH)
raw = []
for item in raw:
if not isinstance(item, dict):
continue
code = str(item.get("code") or "").strip()
name = str(item.get("language") or "").strip()
if not code:
continue
code_to_name.setdefault(code, name or code)
for candidate in (code, name, *(item.get("aliases") or [])):
folded = _fold(str(candidate))
if folded and folded not in LANGUAGE_PLACEHOLDERS:
alias_to_code.setdefault(folded, code)
_ALIAS_TO_CODE = alias_to_code
_CODE_TO_NAME = code_to_name
return alias_to_code, code_to_name
def normalize_language(value: object) -> str | None:
"""Resolve any known spelling of a language to its ISO 639-1 code.
Accepts a two-letter code, an ISO 639-2 three-letter code in either the
bibliographic or terminological form, or an English name. Returns None for
anything unrecognised or for the placeholders a source uses to say it does
not know, so callers can treat "no language" uniformly.
"""
if value is None:
return None
folded = _fold(str(value))
if not folded or folded in LANGUAGE_PLACEHOLDERS:
return None
alias_to_code, _ = _load()
return alias_to_code.get(folded)
def language_name(code: str | None) -> str | None:
"""Return the English name for a language code, or None if unknown."""
if not code:
return None
_, code_to_name = _load()
return code_to_name.get(str(code).strip())
def language_alias_map() -> dict[str, str]:
"""Every known alias mapped to its code, for callers doing their own matching.
Direct Download scans free-text paths and needs the whole alias set up front
to look for, rather than resolving one candidate at a time.
"""
alias_to_code, _ = _load()
return dict(alias_to_code)
def supported_book_languages() -> list[dict[str, str]]:
"""The selectable languages, as ``{"language": ..., "code": ...}``.
Aliases are an implementation detail of resolution, so they are left out of
what the settings dropdown and the API hand to clients.
"""
_, code_to_name = _load()
return [{"language": name, "code": code} for code, name in code_to_name.items()]
def known_language_codes() -> frozenset[str]:
"""Every ISO 639-1 code the bundled language data defines."""
_, code_to_name = _load()
return frozenset(code_to_name)
+22 -10
View File
@@ -4,6 +4,7 @@ import logging
import sys
from collections.abc import Mapping
from logging.handlers import RotatingFileHandler
from threading import Lock
from typing import TYPE_CHECKING
from shelfmark.config.env import ENABLE_LOGGING, LOG_FILE, LOG_LEVEL
@@ -12,6 +13,10 @@ if TYPE_CHECKING:
from pathlib import Path
_file_handlers: dict[Path, RotatingFileHandler] = {}
_file_handlers_lock = Lock()
class CustomLogger(logging.Logger):
"""Custom logger class with additional error_trace method."""
@@ -122,6 +127,22 @@ def _normalize_log_extra(value: object) -> Mapping[str, object] | None:
return None
def _get_file_handler(log_file: Path, formatter: logging.Formatter) -> RotatingFileHandler:
"""Return the process-wide rotating handler for a log file."""
with _file_handlers_lock:
handler = _file_handlers.get(log_file)
if handler is None:
log_file.parent.mkdir(parents=True, exist_ok=True)
handler = RotatingFileHandler(
log_file,
maxBytes=10485760, # 10MB
backupCount=5,
)
handler.setFormatter(formatter)
_file_handlers[log_file] = handler
return handler
def setup_logger(name: str, log_file: Path = LOG_FILE) -> CustomLogger:
"""Set up and configure a logger instance.
@@ -163,16 +184,7 @@ def setup_logger(name: str, log_file: Path = LOG_FILE) -> CustomLogger:
# File handler if log file is specified
try:
if ENABLE_LOGGING:
# Create log directory if it doesn't exist
log_dir = log_file.parent
log_dir.mkdir(parents=True, exist_ok=True)
file_handler = RotatingFileHandler(
log_file,
maxBytes=10485760, # 10MB
backupCount=5,
)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logger.addHandler(_get_file_handler(log_file, formatter))
except (OSError, TypeError, ValueError) as e:
logger.error_trace(f"Failed to create log file: {e}", exc_info=True)
+11
View File
@@ -97,6 +97,7 @@ class DownloadTask:
year: str | None = None
format: str | None = None
size: str | None = None
downloads: int | None = None # Download count from source
preview: str | None = None
content_type: str | None = None # "book (fiction)", "audiobook", "magazine", etc.
source_url: str | None = None # Original release URL used by source-specific handlers
@@ -108,6 +109,9 @@ class DownloadTask:
retry_expected_hash: str | None = None # Optional torrent hash used to match client downloads
retry_ratio_limit: float | None = None # Optional post-download seeding ratio
retry_seeding_time_limit_minutes: int | None = None # Optional post-download seeding time limit
retry_source_context: dict[str, Any] = field(
default_factory=dict
) # Source-private context for retry/re-resolution
can_retry_without_staged_source: bool = (
True # Whether the source can restart without a preserved staged file
)
@@ -116,6 +120,7 @@ class DownloadTask:
series_name: str | None = None
series_position: float | None = None # Float for novellas (e.g., 1.5)
subtitle: str | None = None # Book subtitle for naming templates
language: str | None = None # Release language code for the {Language} template variable
# Hardlinking support
original_download_path: str | None = None # Path in download client (for hardlinking)
@@ -132,6 +137,12 @@ class DownloadTask:
default_factory=dict
) # Per-output parameters (e.g. email recipient)
# Multi-book packs: one release holding several books. `book_plan` is the split the
# user approved before download (list of {title, series_position, year, files});
# `multi_book` asks post-processing to split heuristically when no plan exists.
multi_book: bool = False
book_plan: list[dict[str, Any]] | None = None
# User association (multi-user support)
user_id: int | None = None # DB user ID who queued this download
username: str | None = None # Username for {User} template variable
+68 -5
View File
@@ -4,6 +4,7 @@ import re
from pathlib import Path
from typing import TYPE_CHECKING
from shelfmark.core.languages import LANGUAGE_PLACEHOLDERS, normalize_language
from shelfmark.core.logger import setup_logger
if TYPE_CHECKING:
@@ -13,12 +14,14 @@ logger = setup_logger(__name__)
# Known variable tokens, sorted longest-first to avoid partial matches
# e.g., "SeriesPosition" must match before "Series"
# e.g., "SeriesPosition" must match before "Series", "FirstAuthor" before "Author"
KNOWN_TOKENS = [
"seriesposition",
"primarytitle",
"originalname",
"firstauthor",
"partnumber",
"language",
"subtitle",
"author",
"series",
@@ -27,12 +30,22 @@ KNOWN_TOKENS = [
"user",
]
# Authors reach naming already joined as "First Author, Second Author, ...".
# {FirstAuthor} keeps only the first entry. A single name written "Last, First"
# is split on the comma too and renders as "Last" -- the source metadata does
# not mark which form it is (see #930).
AUTHOR_LIST_SEPARATOR = re.compile(r"\s*[,;]\s*")
# Match any {...} block for template parsing
BRACE_PATTERN = re.compile(r"\{([^}]+)\}")
# Characters that are invalid in filenames on various filesystems
INVALID_CHARS = re.compile(r'[\\/:*?"<>|]')
# Runs of whitespace inside a single placeholder's rendered value, e.g. "Conan Doyle"
# -- collapsed to the configured word separator (see `parse_naming_template`).
WHITESPACE_RUN = re.compile(r"\s+")
def _sanitize(name: str | None, max_length: int = 245) -> str:
"""Sanitize a string for filesystem use."""
@@ -54,6 +67,14 @@ def sanitize_filename(name: str | None, max_length: int = 245) -> str:
sanitize_path_component = sanitize_filename
def first_author(value: object) -> str:
"""Return the first entry from an author string joined with ',' or ';'."""
text = " ".join(str(value or "").split())
if not text:
return ""
return AUTHOR_LIST_SEPARATOR.split(text, maxsplit=1)[0].strip()
def format_series_position(position: str | float | None) -> str:
"""Format a series position for naming templates."""
if position is None:
@@ -66,6 +87,33 @@ def format_series_position(position: str | float | None) -> str:
return str(position)
def normalize_language_code(language: str | None) -> str:
"""Resolve a release language to the single spelling used in a path.
Sources report the same language in different shapes: "en", "eng", "English".
All of them have to collapse to one code, or the editions they identify end
up in separate folders, which is the collision this token exists to prevent.
Placeholder values render empty so `{ (Language)}` disappears entirely
rather than labelling a folder "(unknown)".
A language the bundled data does not know is kept, casefolded, rather than
dropped: it still separates editions, and it cannot collide with a resolved
code precisely because nothing resolves it.
"""
if not language:
return ""
resolved = normalize_language(language)
if resolved is not None:
return resolved
normalized = " ".join(str(language).split()).strip().casefold()
if normalized in LANGUAGE_PLACEHOLDERS:
return ""
return normalized
def derive_primary_title(title: str | None, subtitle: str | None) -> str:
"""Return the title without an explicit subtitle suffix when possible."""
title_value = " ".join(str(title or "").split()).strip()
@@ -91,8 +139,7 @@ PAD_NUMBERS_PATTERN = re.compile(r"\d+")
def natural_sort_key(path: str | Path) -> str:
"""Generate a sort key with padded numbers for natural sorting."""
filename = Path(path).name.lower()
return PAD_NUMBERS_PATTERN.sub(lambda m: m.group().zfill(9), filename)
return PAD_NUMBERS_PATTERN.sub(lambda m: m.group().zfill(9), str(path).lower())
def assign_part_numbers(
@@ -115,8 +162,16 @@ def parse_naming_template(
metadata: Mapping[str, str | int | float | None],
*,
allow_path_separators: bool = True,
word_separator: str = " ",
) -> str:
"""Render a naming template with Shelfmark metadata placeholders."""
"""Render a naming template with Shelfmark metadata placeholders.
`word_separator` replaces whitespace *inside* each placeholder's rendered
value (e.g. "Conan Doyle" -> "Conan.Doyle" for a "." separator). It never
touches literal characters typed into the template itself, so a template
like "{Author}.-.{Title}" keeps its own dots regardless of this setting.
The default (" ") leaves values untouched, matching prior behavior.
"""
if not template:
return ""
@@ -135,6 +190,8 @@ def parse_naming_template(
value = normalized.get(placeholder_name)
if placeholder_name == "seriesposition":
value = format_series_position(value)
elif placeholder_name == "firstauthor" and not value:
value = first_author(normalized.get("author"))
if value is None:
return ""
return str(value).strip()
@@ -150,6 +207,8 @@ def parse_naming_template(
if not value:
return ""
if word_separator != " ":
value = WHITESPACE_RUN.sub(word_separator, value)
if not allow_path_separators:
value = value.replace("/", "_")
value = sanitize_filename(value)
@@ -215,9 +274,13 @@ def build_library_path(
template: str,
metadata: Mapping[str, str | int | float | None],
extension: str | None = None,
*,
word_separator: str = " ",
) -> Path:
"""Build a final library path from a template and metadata."""
relative = parse_naming_template(template, metadata, allow_path_separators=True)
relative = parse_naming_template(
template, metadata, allow_path_separators=True, word_separator=word_separator
)
if not relative:
# Fallback to title if template produces empty result
+34 -1
View File
@@ -33,6 +33,11 @@ logger = setup_logger(__name__)
oauth = OAuth()
_RETURN_TO_SESSION_KEY = "oidc_return_to"
_OIDC_CLIENT_ERRORS = (OAuthError, OSError, RuntimeError, TypeError, ValueError)
_EMPTY_JWKS_MESSAGE = (
"Authentication failed: the identity provider returned no token signing keys "
"(empty JWKS). If you use Authentik, select a Signing Key in the provider "
"settings and try again."
)
class _ClaimsMappingLike(Protocol):
@@ -104,7 +109,7 @@ def _normalize_return_to(raw_return_to: object) -> str | None:
return None
parsed = urlsplit(value)
if parsed.scheme or parsed.netloc:
if parsed.scheme or parsed.netloc or "\\" in parsed.path:
return None
script_root = request.script_root.rstrip("/")
@@ -121,6 +126,17 @@ def _normalize_return_to(raw_return_to: object) -> str | None:
return urlunsplit(("", "", path, parsed.query, parsed.fragment))
def _idp_jwks_has_no_keys(client: Any) -> bool:
"""Return True when the IdP's JWKS document verifiably contains no signing keys."""
try:
jwk_set = client.fetch_jwk_set(force=True)
except (*_OIDC_CLIENT_ERRORS, KeyError):
return False
if not isinstance(jwk_set, Mapping):
return False
return not jwk_set.get("keys")
def _get_pending_return_to(*, clear: bool = False) -> str | None:
"""Read the pending post-login target from the session."""
raw_return_to = (
@@ -274,6 +290,17 @@ def register_oidc_routes(app: Flask, user_db: UserDB) -> None:
return redirect(
_login_error_url(f"OIDC token claim validation failed: {claim_name}")
)
except KeyError, ValueError:
# An IdP serving an empty JWKS document (e.g. an Authentik provider
# with no Signing Key selected) surfaces as KeyError('keys') while
# importing the key set. Test Connection only validates discovery,
# so this is the first place the misconfiguration becomes visible.
if _idp_jwks_has_no_keys(client):
logger.exception(
"OIDC callback failed: the IdP JWKS document contains no signing keys"
)
return redirect(_login_error_url(_EMPTY_JWKS_MESSAGE))
raise
claims = _normalize_claims(token.get("userinfo"))
# If userinfo is missing or claims are too sparse, request it explicitly.
@@ -306,6 +333,12 @@ def register_oidc_routes(app: Flask, user_db: UserDB) -> None:
is_admin = admin_group in groups
allow_email_link = bool(user_info.get("email")) and _is_email_verified(claims)
if user_info.get("email") and not allow_email_link:
logger.debug(
"OIDC email %s is not marked verified by the IdP; skipping "
"email-based account linking",
user_info["email"],
)
user = provision_oidc_user(
user_db,
user_info,
+32 -20
View File
@@ -145,28 +145,36 @@ class BookQueue:
with self._lock:
self._queue_hook = hook
def update_status(self, book_id: str, status: QueueStatus) -> None:
"""Update status of a book in the queue."""
def _apply_status_locked(
self, book_id: str, status: QueueStatus
) -> tuple[Callable[[str, QueueStatus, DownloadTask], None] | None, DownloadTask | None]:
"""Apply a status change; returns the terminal hook to run after releasing the lock."""
hook: Callable[[str, QueueStatus, DownloadTask], None] | None = None
hook_task: DownloadTask | None = None
previous_status = self._status.get(book_id)
self._update_status(book_id, status)
if (
status in TERMINAL_QUEUE_STATUSES
and previous_status != status
and self._terminal_status_hook is not None
):
current_task = self._task_data.get(book_id)
if current_task is not None:
hook = self._terminal_status_hook
hook_task = current_task
# Clean up active download tracking when finished
if status in TERMINAL_QUEUE_STATUSES:
self._active_downloads.pop(book_id, None)
self._cancel_flags.pop(book_id, None)
return hook, hook_task
def update_status(self, book_id: str, status: QueueStatus) -> None:
"""Update status of a book in the queue."""
with self._lock:
previous_status = self._status.get(book_id)
self._update_status(book_id, status)
if (
status in TERMINAL_QUEUE_STATUSES
and previous_status != status
and self._terminal_status_hook is not None
):
current_task = self._task_data.get(book_id)
if current_task is not None:
hook = self._terminal_status_hook
hook_task = current_task
# Clean up active download tracking when finished
if status in TERMINAL_QUEUE_STATUSES:
self._active_downloads.pop(book_id, None)
self._cancel_flags.pop(book_id, None)
hook, hook_task = self._apply_status_locked(book_id, status)
if hook is not None and hook_task is not None:
hook(book_id, status, hook_task)
@@ -257,7 +265,11 @@ class BookQueue:
# Not in a cancellable state
return False
self.update_status(task_id, QueueStatus.CANCELLED)
# Write under the same lock so a download that finishes first is not overwritten
hook, hook_task = self._apply_status_locked(task_id, QueueStatus.CANCELLED)
if hook is not None and hook_task is not None:
hook(task_id, QueueStatus.CANCELLED, hook_task)
return True
def set_priority(self, task_id: str, new_priority: int) -> bool:
+102
View File
@@ -0,0 +1,102 @@
"""Pre-download release inspection: list a release's files and plan a multi-book split."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from flask import jsonify, request
from shelfmark.core.logger import setup_logger
from shelfmark.core.utils import is_audiobook
from shelfmark.download.postprocess.packs import PackFile, PackPlan, plan_pack
from shelfmark.download.postprocess.policy import (
get_supported_audiobook_formats,
get_supported_formats,
)
from shelfmark.release_sources import get_handler
if TYPE_CHECKING:
from collections.abc import Callable
from flask import Flask, Response
logger = setup_logger(__name__)
_INSPECT_ERRORS = (OSError, RuntimeError, ValueError, TypeError, KeyError, AttributeError)
NOT_INSPECTABLE_REASON = "This source cannot list the release's files before downloading"
def _serialize_plan(plan: PackPlan) -> dict[str, Any]:
return {
"is_pack": plan.is_pack,
"ignored": plan.ignored,
"books": [
{
"title": book.title,
"series_position": book.series_position,
"year": book.year,
"files": book.files,
}
for book in plan.books
],
}
def inspect_release(data: dict[str, Any]) -> dict[str, Any]:
"""Build the inspect response for a release payload (same shape as a download)."""
source = str(data["source"])
handler = get_handler(source)
try:
files: list[PackFile] | None = handler.list_files(data)
except _INSPECT_ERRORS as exc:
logger.warning(
"Could not list files for %s release %s: %s", source, data.get("source_id"), exc
)
return {"inspected": False, "reason": str(exc), "files": [], "plan": None}
if files is None:
return {"inspected": False, "reason": NOT_INSPECTABLE_REASON, "files": [], "plan": None}
content_type = data.get("content_type")
supported = (
get_supported_audiobook_formats()
if is_audiobook(content_type if isinstance(content_type, str) else None)
else get_supported_formats()
)
series_name = data.get("series_name")
author_name = data.get("author")
plan = plan_pack(
files,
supported_extensions=set(supported),
series_name=series_name if isinstance(series_name, str) else None,
author_name=author_name if isinstance(author_name, str) else None,
)
return {
"inspected": True,
"reason": None,
"files": [{"path": f.path, "size": f.size} for f in files],
"plan": _serialize_plan(plan),
}
def register_release_inspect_routes(
app: Flask,
login_required: Callable[..., Any],
) -> None:
"""Register POST /api/releases/inspect."""
@app.route("/api/releases/inspect", methods=["POST"])
@login_required
def api_inspect_release() -> Response | tuple[Response, int]:
data = request.get_json(silent=True)
if not isinstance(data, dict):
return jsonify({"error": "No data provided"}), 400
if not data.get("source_id"):
return jsonify({"error": "source_id is required"}), 400
if not data.get("source"):
return jsonify({"error": "source is required"}), 400
try:
get_handler(str(data["source"]))
except ValueError as exc:
return jsonify({"error": str(exc)}), 400
return jsonify(inspect_release(data))
+93
View File
@@ -0,0 +1,93 @@
"""Shared release-search helpers.
Extracted from the ``/api/releases`` route so the same per-source search logic can
be reused outside the HTTP route (for example by background automation) without
going through Flask. Behaviour for the HTTP route is preserved: the route delegates
its inner per-source search to :func:`search_source_releases`.
"""
from __future__ import annotations
import sqlite3
from typing import TYPE_CHECKING
from shelfmark.core.logger import setup_logger
from shelfmark.core.search_plan import build_release_search_plan
if TYPE_CHECKING:
from shelfmark.core.models import SearchFilters
from shelfmark.metadata_providers import BookMetadata
from shelfmark.release_sources import Release, ReleaseSource
logger = setup_logger(__name__)
# Mirror of main._OPERATIONAL_ERRORS so a misbehaving source can't crash a caller.
_OPERATIONAL_ERRORS = (OSError, RuntimeError, TypeError, ValueError, sqlite3.Error)
def search_source_releases(
source_name: str,
search_book: BookMetadata,
*,
languages: list[str] | None = None,
manual_query: str | None = None,
indexers: list[str] | None = None,
expand_search: bool = False,
content_type: str = "ebook",
source_filters: SearchFilters | None = None,
user_id: int | None = None,
) -> tuple[ReleaseSource | None, list[Release], str | None]:
"""Search a single release source, returning any error instead of raising.
Returns ``(source, releases, error_message)``. On failure ``source`` is ``None``
and ``error_message`` describes the problem. ``user_id`` lets the search plan
pick up that user's default languages when no explicit filter is given.
"""
from shelfmark.release_sources import SourceUnavailableError, get_source
try:
source = get_source(source_name)
plan = build_release_search_plan(
search_book,
languages=languages,
manual_query=manual_query,
indexers=indexers,
source_filters=source_filters,
user_id=user_id,
)
if plan.source_filters is not None:
planned_query = plan.manual_query or plan.primary_query
planned_query_type = "query"
elif plan.manual_query:
planned_query = plan.manual_query
planned_query_type = "manual"
elif not expand_search and plan.isbn_candidates:
planned_query = plan.isbn_candidates[0]
planned_query_type = "isbn"
else:
planned_query = plan.primary_query
planned_query_type = "title_author"
logger.debug(
"Searching %s: %s='%s' (title='%s', authors=%s, expand=%s, content_type=%s)",
source_name,
planned_query_type,
planned_query,
search_book.title,
search_book.authors,
expand_search,
content_type,
)
releases = source.search(
search_book, plan, expand_search=expand_search, content_type=content_type
)
except ValueError:
return None, [], f"Unknown source: {source_name}"
except (SourceUnavailableError, *_OPERATIONAL_ERRORS) as exc:
logger.warning("Release search failed for source %s: %s", source_name, exc)
return None, [], f"{source_name}: {exc!s}"
else:
return source, releases, None
+2
View File
@@ -715,6 +715,8 @@ def register_request_routes(
raw_requests = data.get("requests")
if not isinstance(raw_requests, list) or len(raw_requests) == 0:
return jsonify({"error": "requests must contain at least one request"}), 400
if not all(isinstance(raw_request, dict) for raw_request in raw_requests):
return jsonify({"error": "requests must contain objects"}), 400
try:
prepared_requests = [
+134
View File
@@ -0,0 +1,134 @@
"""A wall-clock budget for one release search, enforced through the existing cancel flag.
`/api/releases` is synchronous: the browser waits on it while the search runs. Nothing
bounded that wait, and the bypasser's own worst case is minutes long
(`internal_bypasser.max_duration_seconds()`), so a search that ran into an unsolvable
protection challenge outlived every reverse proxy in front of it. The user then saw
"Server unavailable (504)" - a gateway timeout that says nothing about what went wrong
and points the blame at their proxy config. See issue #1276.
The budget is expressed as the cancel flag the download path already understands: an
Event armed by a timer. `html_get_page`, the bypassers and the helper subprocess all poll
it, so an expired budget stops a solve already in flight rather than only refusing the
next one. When it trips, the search fails with a message that names the real cause.
Scoped to a context variable so it applies to the request that set it and to nothing else
- a queued download must keep its own, much longer, budget.
"""
from __future__ import annotations
import threading
import time
from contextlib import contextmanager
from contextvars import ContextVar
from typing import TYPE_CHECKING
from shelfmark.core.logger import setup_logger
if TYPE_CHECKING:
from collections.abc import Iterator
logger = setup_logger(__name__)
# What one search may spend. A first search on a cold start legitimately pays for a
# browser solve - jfmlima measured 60-120s for a successful one on Anna's Archive - so
# this cannot be as tight as a proxy's default read timeout without breaking working
# setups. It is instead well below the ~840s the bypass path could previously reach,
# which is what turned a failing challenge into a gateway timeout.
DEFAULT_SEARCH_BUDGET_SECONDS = 300.0
_MIN_SEARCH_BUDGET_SECONDS = 30.0
_MAX_SEARCH_BUDGET_SECONDS = 1800.0
# Raised to the caller when the budget runs out, so the API can say so plainly.
SEARCH_DEADLINE_MESSAGE = (
"The release search ran out of time (%.0fs). Anna's Archive is behind a protection "
"challenge the bypasser could not solve in that window. Raise the release search "
"timeout if your setup is simply slow."
)
class SearchDeadline:
"""A budget with an Event that trips when it expires."""
def __init__(self, budget_seconds: float) -> None:
self.budget_seconds = budget_seconds
self.expires_at = time.monotonic() + budget_seconds
# A plain threading.Event on purpose: this is handed on as a cancel flag, and
# that is the type the download path, the CDP worker thread and the bypass helper
# already poll.
self.event = threading.Event()
self._timer = threading.Timer(budget_seconds, self.event.set)
self._timer.daemon = True
def start(self) -> None:
self._timer.start()
def cancel(self) -> None:
self._timer.cancel()
@property
def remaining(self) -> float:
return max(0.0, self.expires_at - time.monotonic())
@property
def expired(self) -> bool:
return self.event.is_set() or self.remaining <= 0
_current: ContextVar[SearchDeadline | None] = ContextVar("search_deadline", default=None)
def budget_seconds() -> float:
"""The configured budget for one release search."""
from shelfmark.core.config import config as app_config
raw = app_config.get("RELEASE_SEARCH_TIMEOUT", DEFAULT_SEARCH_BUDGET_SECONDS)
if isinstance(raw, bool) or not isinstance(raw, int | float | str):
return DEFAULT_SEARCH_BUDGET_SECONDS
try:
value = float(raw)
except TypeError, ValueError:
return DEFAULT_SEARCH_BUDGET_SECONDS
if value <= 0:
return DEFAULT_SEARCH_BUDGET_SECONDS
return min(max(value, _MIN_SEARCH_BUDGET_SECONDS), _MAX_SEARCH_BUDGET_SECONDS)
@contextmanager
def search_deadline(budget: float | None = None) -> Iterator[SearchDeadline]:
"""Apply a budget to everything the calling context does."""
deadline = SearchDeadline(budget if budget is not None else budget_seconds())
token = _current.set(deadline)
deadline.start()
logger.debug("Release search budget: %.0fs", deadline.budget_seconds)
try:
yield deadline
finally:
deadline.cancel()
_current.reset(token)
def current() -> SearchDeadline | None:
"""The budget in force, or None outside a search."""
return _current.get()
def expired() -> bool:
"""Whether the budget in force has run out. False when there is no budget."""
deadline = _current.get()
return deadline is not None and deadline.expired
def cancel_event() -> threading.Event | None:
"""The Event that trips when the budget runs out, for use as a cancel flag."""
deadline = _current.get()
return deadline.event if deadline is not None else None
def deadline_message() -> str:
"""The failure to report when the budget has run out."""
deadline = _current.get()
budget = deadline.budget_seconds if deadline else DEFAULT_SEARCH_BUDGET_SECONDS
return SEARCH_DEADLINE_MESSAGE % budget
+103 -28
View File
@@ -7,6 +7,7 @@ from dataclasses import dataclass
from typing import TYPE_CHECKING
from shelfmark.core.config import config
from shelfmark.core.logger import setup_logger
from shelfmark.metadata_providers import (
BookMetadata,
build_localized_search_titles,
@@ -16,6 +17,8 @@ from shelfmark.metadata_providers import (
if TYPE_CHECKING:
from shelfmark.core.models import SearchFilters
logger = setup_logger(__name__)
MANUAL_QUERY_MAX_LEN = 256
@@ -52,44 +55,110 @@ class ReleaseSearchPlan:
return self.title_variants[0].query if self.title_variants else ""
def _normalize_languages(languages: list[str] | None) -> list[str] | None:
def _to_language_codes(values: Iterable[object], *, source: str) -> list[str] | None:
"""Resolve any spelling of a language to the ISO code the sources expect.
Anna's Archive matches `lang=` against ISO codes: `lang=english` is not a loose
spelling of `lang=en`, it is a facet value AA does not have, and it filters every
search down to nothing. Only the *per-user* override was normalised
(config.users_settings.validate), so a global BOOK_LANGUAGE=english - the spelling
the old docs used - reached the query verbatim and silently emptied every search
with no error anywhere. See issue #1276.
An entry that resolves to nothing is dropped with a warning rather than passed
through: searching unfiltered and saying so beats reporting "no results" for a book
the source is full of.
"""
from shelfmark.core.languages import normalize_language
codes: list[str] = []
unresolved: list[str] = []
for value in values:
text = str(value).strip() if value is not None else ""
if not text:
continue
if text.lower() == "all":
# An explicit "search every language", not a language.
return None
code = normalize_language(text)
if code is None:
unresolved.append(text)
continue
if code not in codes:
codes.append(code)
if unresolved:
logger.warning(
"Ignoring unrecognised language(s) in %s: %s. Use an ISO code such as 'en', "
"a three-letter code, or an English name like 'English'.",
source,
", ".join(unresolved),
)
return codes or None
def _normalize_languages(languages: list[str] | None, user_id: int | None) -> list[str] | None:
if not languages:
default = getattr(config, "BOOK_LANGUAGE", None)
default = config.get("BOOK_LANGUAGE", None, user_id=user_id)
if isinstance(default, str):
default_values: list[object] = [default]
elif isinstance(default, Iterable) and not isinstance(default, (bytes, bytearray, dict)):
default_values = list(default)
else:
return None
return [str(lang).strip() for lang in default_values if str(lang).strip()]
return _to_language_codes(default_values, source="BOOK_LANGUAGE")
normalized: list[str] = []
for lang in languages:
if not lang:
continue
s = str(lang).strip()
if not s:
continue
normalized.append(s)
if any(lang.lower() == "all" for lang in normalized):
return None
return normalized or None
return _to_language_codes(languages, source="the search request")
def _pick_search_author(book: BookMetadata) -> str:
def first_author(value: str) -> str:
"""The first name in a possibly comma-joined author string.
Both ends of the app hand us every contributor in one string. The frontend joins
`authors` with ", " for display (`bookTransformers.ts`) and that display string comes
straight back as the `author` request parameter, while several providers set
`search_author` from the same joined text. Searching a release source for
"Blindness Jose Saramago, Giovanni Pontiero, ..." - the author plus two translators -
matches nothing, and the user is told the book has no releases at all.
A "Last, First" author collapses to the surname, which is still a usable search term
and is what the authors[] fallback has always done with the same input. See #1252.
"""
first, _, _ = value.partition(",")
return first.strip()
def pick_search_author(book: BookMetadata) -> str:
"""The one author a release query should carry, from whichever field holds one.
Every release source that builds its own query wants exactly this, so it lives here
rather than being re-derived per source - the two branches below drifted apart once
already (#1252) and the IRC source carried a third copy of the same preference.
#1290 fixed the same report by merging the two branches and trimming whichever one
won; this keeps that outcome ("Blindness Jose Saramago" from either field, measured
there at 0 releases before and 49 after) and adds the empty-narrowing fallback, so a
credit list that merely starts with a blank entry does not fall out to title-only.
"""
# Narrowing can come back empty - the joined string starts with a comma because the
# first contributor was blank, and `authors.join(', ')` does not drop the empty entry.
# Falling through to authors[] then still finds a usable name; returning "" would
# search by title alone and lose the author we were holding all along.
if book.search_author:
return book.search_author
narrowed = first_author(book.search_author)
if narrowed:
return narrowed
if not book.authors:
return ""
# A bare string here would otherwise be iterated one character at a time; the IRC
# source guarded against exactly that before it shared this helper.
authors = book.authors if isinstance(book.authors, list) else [book.authors or ""]
for author in authors:
narrowed = first_author(author or "")
if narrowed:
return narrowed
first = book.authors[0]
if "," in first:
first = first.split(",")[0].strip()
return first
return ""
def _pick_search_title(book: BookMetadata) -> str:
@@ -102,15 +171,21 @@ def build_release_search_plan(
manual_query: str | None = None,
indexers: list[str] | None = None,
source_filters: SearchFilters | None = None,
user_id: int | None = None,
) -> ReleaseSearchPlan:
"""Build normalized search variants shared across release sources."""
resolved_languages = _normalize_languages(languages)
"""Build normalized search variants shared across release sources.
``user_id`` picks up that user's default languages when the caller does not
filter explicitly, so a search started without a language filter uses the
reader's own default rather than the instance-wide one.
"""
resolved_languages = _normalize_languages(languages, user_id)
resolved_manual_query = None
if manual_query:
resolved_manual_query = manual_query.strip()[:MANUAL_QUERY_MAX_LEN] or None
author = _pick_search_author(book)
author = pick_search_author(book)
base_title = _pick_search_title(book)
if resolved_manual_query:
+11 -4
View File
@@ -302,6 +302,7 @@ def register_self_user_routes(app: Flask, user_db: UserDB) -> None:
auth_source = capabilities["authSource"]
password = data.get("password", "")
password_hash: str | None = None
if password:
if not capabilities["canSetPassword"]:
return jsonify(
@@ -314,7 +315,7 @@ def register_self_user_routes(app: Flask, user_db: UserDB) -> None:
return jsonify(
{"error": f"Password must be at least {MIN_PASSWORD_LENGTH} characters"}
), 400
user_db.update_user(user_id, password_hash=generate_password_hash(password))
password_hash = generate_password_hash(password)
user_fields: dict[str, Any] = {}
if "email" in data:
@@ -363,9 +364,7 @@ def register_self_user_routes(app: Flask, user_db: UserDB) -> None:
if field in user_fields and user_fields[field] == user.get(field):
user_fields.pop(field)
if user_fields:
user_db.update_user(user_id, **user_fields)
validated_settings: dict[str, Any] | None = None
if "settings" in data:
settings_payload = data["settings"]
if not isinstance(settings_payload, dict):
@@ -397,6 +396,14 @@ def register_self_user_routes(app: Flask, user_db: UserDB) -> None:
}
), 400
# Apply the writes only once the whole payload has been accepted.
if password_hash is not None:
user_fields["password_hash"] = password_hash
if user_fields:
user_db.update_user(user_id, **user_fields)
if validated_settings is not None:
user_db.set_user_settings(user_id, validated_settings)
try:
app_config.refresh(force=True)
+4
View File
@@ -9,6 +9,7 @@ from typing import TYPE_CHECKING, Any
from werkzeug.utils import secure_filename
from shelfmark.config.env import normalize_log_level
from shelfmark.core.logger import setup_logger
from shelfmark.core.request_helpers import coerce_bool, normalize_optional_text
@@ -898,6 +899,9 @@ def _get_env_value_for_field(field: FieldBase) -> tuple[bool, object | None]:
"WELIB_MIRROR_URLS",
} and isinstance(parsed, list):
parsed = _normalize_mirror_env_urls(parsed)
if field.key == "LOG_LEVEL" and isinstance(parsed, str):
# LOG_LEVEL is commonly set lowercase; the field options are uppercase.
parsed = normalize_log_level(parsed)
return True, parsed
if field.key == "AA_MIRROR_URLS":
+32 -1
View File
@@ -87,7 +87,8 @@ CREATE TABLE IF NOT EXISTS download_history (
download_path TEXT,
retry_payload TEXT,
queued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
terminal_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
terminal_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
downloads INTEGER
);
CREATE INDEX IF NOT EXISTS idx_download_history_user_status
@@ -203,6 +204,7 @@ class UserDB:
self._migrate_request_delivery_columns(conn)
self._migrate_download_history_queued_at(conn)
self._migrate_download_history_retry_payload(conn)
self._migrate_download_history_downloads(conn)
conn.commit()
# WAL mode must be changed outside an open transaction.
conn.execute("PRAGMA journal_mode=WAL")
@@ -270,6 +272,13 @@ class UserDB:
if "retry_payload" not in column_names:
conn.execute("ALTER TABLE download_history ADD COLUMN retry_payload TEXT")
def _migrate_download_history_downloads(self, conn: sqlite3.Connection) -> None:
"""Ensure download_history.downloads exists for download count persistence."""
columns = conn.execute("PRAGMA table_info(download_history)").fetchall()
column_names = {str(col["name"]) for col in columns}
if "downloads" not in column_names:
conn.execute("ALTER TABLE download_history ADD COLUMN downloads INTEGER")
def create_user(
self,
username: str,
@@ -344,6 +353,7 @@ class UserDB:
_ALLOWED_UPDATE_COLUMNS: ClassVar[frozenset[str]] = frozenset(
{
"username",
"email",
"display_name",
"password_hash",
@@ -353,6 +363,7 @@ class UserDB:
}
)
_USER_UPDATE_STATEMENTS: ClassVar[dict[str, str]] = {
"username": "UPDATE users SET username = ? WHERE id = ?",
"email": "UPDATE users SET email = ? WHERE id = ?",
"display_name": "UPDATE users SET display_name = ? WHERE id = ?",
"password_hash": "UPDATE users SET password_hash = ? WHERE id = ?",
@@ -422,6 +433,26 @@ class UserDB:
finally:
conn.close()
def get_first_admin(self) -> dict[str, Any] | None:
"""Return the lowest-id admin user, or None. Used as the identity for SHELFMARK_API_KEY requests."""
conn = self._connect()
try:
row = conn.execute(
"SELECT * FROM users WHERE role = 'admin' ORDER BY id LIMIT 1"
).fetchone()
return dict(row) if row else None
finally:
conn.close()
def has_admin(self) -> bool:
"""Return True when at least one admin user exists."""
conn = self._connect()
try:
row = conn.execute("SELECT 1 FROM users WHERE role = 'admin' LIMIT 1").fetchone()
return row is not None
finally:
conn.close()
def has_admin_with_password(self) -> bool:
"""Return True when at least one admin user with a password hash exists."""
conn = self._connect()
+20
View File
@@ -115,6 +115,26 @@ def is_audiobook(content_type: str | None) -> bool:
return bool(content_type and "audiobook" in content_type.lower())
# Every audio format an audiobook can legitimately arrive in, and the single source of
# truth for that list. The settings UI, release-source parsing, archive extraction and
# post-download scanning all derive from it, so a format added here becomes selectable,
# searchable AND downloadable at once. These used to be four hand-maintained copies that
# had drifted apart: the settings UI only offered m4b/mp3/m4a, which meant a FLAC
# audiobook could never be enabled, was silently dropped from every search result, and
# was rejected after download as "format not supported".
#
# "mp4" is here because some trackers (MyAnonamouse in particular) ship AAC audiobooks
# as per-chapter .mp4 files - the same ISO-BMFF container as .m4a/.m4b, just with the
# generic extension. Without it those releases downloaded fine and then failed
# post-processing with "No book files found in download".
AUDIOBOOK_FORMATS = ("m4b", "mp3", "m4a", "mp4", "flac", "ogg", "wma", "aac", "wav", "opus")
# Multi-file audiobooks are almost always distributed as an archive. These are containers
# rather than formats: they are what a *release* looks like, and the formats above are
# what comes out of one after extraction.
ARCHIVE_FORMATS = ("zip", "rar")
CONTENT_TYPES = [
"book (fiction)",
"book (non-fiction)",
+81
View File
@@ -0,0 +1,81 @@
"""Stall-detection grace signalling for long single-shot download operations.
The orchestrator cancels a download after `STALL_TIMEOUT` seconds without activity, where
"activity" means a *changed* status event or a *changed* progress value. That de-duplication
is deliberate - a keep-alive that repeats the same payload on a timer proves nothing about
whether the operation is still making progress, so letting it refresh the stall clock would
make a genuinely wedged download immortal.
Operations that legitimately take longer than `STALL_TIMEOUT` but cannot report incremental
progress therefore declare an explicit upper bound up front instead:
request_activity_grace(status_callback, my_worst_case_seconds)
try:
...one long blocking call...
finally:
release_activity_grace(status_callback)
The grace is a single absolute deadline. It is never extended, so the operation still dies
if it overruns its own declared budget - just at *its* bound rather than at a global 300s.
The signal rides on the existing `status_callback` channel using a sentinel status, which
avoids threading a new parameter through every handler, post-processor and output module.
`shelfmark.download.orchestrator`'s per-task `status_callback` closure intercepts the
sentinel and never forwards it to `update_download_status`.
Adopters should be operations that yield to the gevent hub while blocking (`requests`,
patched `subprocess`). An operation that blocks the hub outright - `shutil.copy2`, sqlite -
will still be killed by the gunicorn worker timeout regardless of any grace, and must go
through `shelfmark.download.fs.run_blocking_io` first.
Current adopters: `shelfmark.download.http.html_get_page` (protection bypass).
Candidates: `download.clients.base_handler._wait_for_completed_path`, archive extraction in
`download.postprocess.scan`, large-file copies in `download.outputs.folder`, email/BookLore
uploads, and the Anna's Archive countdown in `release_sources.direct_download` (which today
refreshes the stall clock on every tick of a loop that proves nothing about the remote).
"""
from collections.abc import Callable
# Not a QueueStatus value, so `update_download_status` would reject it anyway; the
# orchestrator's status_callback intercepts it before that point.
ACTIVITY_GRACE_STATUS = "__activity_grace__"
StatusCallback = Callable[[str, str | None], None]
# A status_callback is caller-supplied and may raise; a failed liveness hint must never
# break the operation it was protecting. Mirrors http._STATUS_CALLBACK_ERRORS.
_CALLBACK_ERRORS = (AttributeError, KeyError, OSError, RuntimeError, TypeError, ValueError)
def request_activity_grace(status_callback: StatusCallback | None, seconds: float) -> None:
"""Ask the orchestrator to suppress stall detection for up to `seconds` from now."""
_emit(status_callback, seconds)
def release_activity_grace(status_callback: StatusCallback | None) -> None:
"""Drop any outstanding grace and count now as activity."""
_emit(status_callback, 0)
def parse_activity_grace(status: str, message: str | None) -> float | None:
"""Return the requested grace in seconds, or None if this is not a grace event.
Never raises: a malformed sentinel is treated as "not a grace event" so a bad emitter
cannot take down the status pipeline.
"""
if status != ACTIVITY_GRACE_STATUS:
return None
try:
return max(float(message or 0), 0.0)
except TypeError, ValueError:
return 0.0
def _emit(status_callback: StatusCallback | None, seconds: float) -> None:
if status_callback is None:
return
try:
status_callback(ACTIVITY_GRACE_STATUS, str(float(seconds)))
except _CALLBACK_ERRORS:
return
+2 -1
View File
@@ -7,6 +7,7 @@ from pathlib import Path
from typing import TYPE_CHECKING, cast
from shelfmark.core.logger import setup_logger
from shelfmark.core.utils import AUDIOBOOK_FORMATS
from shelfmark.core.utils import is_audiobook as check_audiobook
from shelfmark.download.fs import atomic_move
from shelfmark.download.postprocess.policy import (
@@ -98,7 +99,7 @@ ALL_EBOOK_EXTENSIONS = {
}
# All known audio extensions (superset of what user might enable for audiobooks)
ALL_AUDIO_EXTENSIONS = {".m4b", ".mp3", ".m4a", ".aac", ".flac", ".ogg", ".wma", ".wav", ".opus"}
ALL_AUDIO_EXTENSIONS = {f".{fmt}" for fmt in AUDIOBOOK_FORMATS}
def _filter_files(
+31 -2
View File
@@ -190,6 +190,8 @@ class DownloadClient(ABC):
# Class attributes that subclasses must define
protocol: str
name: str
handoff_only = False
prefers_torrent_file = False
def _log_error(self, method: str, e: Exception, level: str = "error") -> str:
"""Log a client error with consistent formatting.
@@ -325,6 +327,19 @@ class DownloadClient(ABC):
"""
def set_category(self, download_id: str, category: str) -> bool:
"""Update a download's category or label when supported by the client.
Args:
download_id: The client-specific download ID.
category: Category or label to assign.
Returns:
True if the category was updated, otherwise False.
"""
return False
@abstractmethod
def get_download_path(self, download_id: str) -> str | None:
"""Get the path where files were downloaded.
@@ -359,12 +374,17 @@ class DownloadClient(ABC):
# Client registry: protocol -> list of client classes
_CLIENTS: dict[str, list[type[DownloadClient]]] = {}
ClientType = TypeVar("ClientType", bound=DownloadClient)
_BUILTIN_CLIENT_MODULES = (
"shelfmark.download.clients.alldebrid",
"shelfmark.download.clients.blackhole",
"shelfmark.download.clients.deluge",
"shelfmark.download.clients.nzbget",
"shelfmark.download.clients.qbittorrent",
"shelfmark.download.clients.realdebrid",
"shelfmark.download.clients.rtorrent",
"shelfmark.download.clients.sabnzbd",
"shelfmark.download.clients.torbox",
"shelfmark.download.clients.transmission",
)
_builtin_client_state = {"loaded": False}
@@ -383,7 +403,7 @@ def _ensure_builtin_clients_registered() -> None:
def register_client(
protocol: str,
) -> Callable[[type[DownloadClient]], type[DownloadClient]]:
) -> Callable[[type[ClientType]], type[ClientType]]:
"""Register a download client for a protocol.
Multiple clients can be registered for the same protocol.
@@ -399,7 +419,7 @@ def register_client(
"""
def decorator(cls: type[DownloadClient]) -> type[DownloadClient]:
def decorator(cls: type[ClientType]) -> type[ClientType]:
if protocol not in _CLIENTS:
_CLIENTS[protocol] = []
_CLIENTS[protocol].append(cls)
@@ -451,6 +471,15 @@ def list_configured_clients() -> list[str]:
return result
def client_prefers_torrent_file(protocol: str) -> bool:
"""Whether the active client needs a fetched .torrent file instead of a magnet."""
_ensure_builtin_clients_registered()
return any(
client_cls.prefers_torrent_file and client_cls.is_configured()
for client_cls in _CLIENTS.get(protocol, [])
)
def get_all_clients() -> dict[str, list[type[DownloadClient]]]:
"""Get all registered client classes.
+718
View File
@@ -0,0 +1,718 @@
"""AllDebrid debrid service client for Shelfmark.
Routes magnet links through the AllDebrid API (v4/v4.1) to download
torrent content via AllDebrid's CDN infrastructure.
"""
from __future__ import annotations
import shutil
import threading
import time
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, ClassVar, NoReturn
from urllib.parse import quote
import requests
from shelfmark.config.env import TMP_DIR
from shelfmark.core.config import config
from shelfmark.core.logger import setup_logger
from shelfmark.download.clients import (
DownloadClient,
DownloadState,
DownloadStatus,
register_client,
)
from shelfmark.download.clients._coercion import config_text
from shelfmark.download.clients.torrent_utils import (
DebridMagnet,
DebridUpload,
resolve_debrid_upload,
)
from shelfmark.download.http import download_url
from shelfmark.download.network import get_ssl_verify
logger = setup_logger(__name__)
_API_BASE = "https://api.alldebrid.com/v4"
_AGENT = "shelfmark"
_ALLDEBRID_CLIENT_ERRORS = (
AttributeError,
OSError,
requests.exceptions.RequestException,
RuntimeError,
TypeError,
ValueError,
)
# AllDebrid magnet status codes (from API v4.1 documentation).
_STATUS_DOWNLOADING = frozenset({0, 1, 2, 3})
_STATUS_READY = 4
# Timeouts and retry limits for API calls.
_API_TIMEOUT = 30
_STATUS_TIMEOUT = 15
_DELAYED_POLL_INTERVAL = 5
_DELAYED_POLL_MAX_ATTEMPTS = 12
# File extensions recognised as book or audiobook content.
_BOOK_EXTENSIONS = (
".aac",
".azw",
".azw3",
".cbr",
".cbz",
".djvu",
".doc",
".docx",
".epub",
".fb2",
".flac",
".lit",
".m4a",
".m4b",
".mobi",
".mp3",
".mp4",
".ogg",
".opus",
".pdf",
".rtf",
".txt",
".wma",
)
def _flatten_magnet_files(
entries: list[dict[str, Any]],
prefix: str = "",
) -> list[dict[str, Any]]:
"""Flatten AllDebrid's nested file tree into a list of file dicts.
AllDebrid returns files with ``"n"`` (name), ``"s"`` (size),
``"l"`` (link), and ``"e"`` (children) keys. Directories use
``"e"`` to nest their contents.
Returns:
List of ``{"filename": ..., "size": ..., "link": ...}`` dicts.
"""
flat: list[dict[str, Any]] = []
for entry in entries:
name = entry.get("n", "")
if "e" in entry:
flat.extend(
_flatten_magnet_files(entry["e"], prefix=f"{prefix}{name}/"),
)
elif entry.get("l"):
flat.append(
{
"filename": f"{prefix}{name}",
"size": entry.get("s", 0),
"link": entry["l"],
}
)
return flat
def _raise_runtime_error(message: str) -> NoReturn:
raise RuntimeError(message)
@dataclass
class _DownloadState:
"""Internal mutable state for an in-progress AllDebrid download."""
magnet_id: str
name: str
target_dir: Path
phase: str = "uploading"
error_message: str | None = None
progress: float = 0.0
download_thread: threading.Thread | None = None
lock: threading.Lock = field(default_factory=threading.Lock)
@register_client("torrent")
class AllDebridClient(DownloadClient):
"""AllDebrid debrid service client.
Downloads torrent content by uploading magnet links to AllDebrid,
waiting for the torrent to complete on their servers, then fetching
the resulting files via direct HTTP download from AllDebrid's CDN.
API documentation: https://docs.alldebrid.com/
"""
protocol = "torrent"
name = "alldebrid"
_downloads: ClassVar[dict[str, _DownloadState]] = {}
_downloads_lock = threading.Lock()
def __init__(self) -> None:
self._api_key = config_text(config.get("ALLDEBRID_API_KEY", ""))
def _auth_headers(self) -> dict[str, str]:
"""Return Authorization header dict for API requests."""
return {"Authorization": f"Bearer {self._api_key}"}
# ------------------------------------------------------------------
# DownloadClient interface
# ------------------------------------------------------------------
@staticmethod
def is_configured() -> bool:
"""Return True when AllDebrid is selected and an API key exists."""
client = config_text(config.get("PROWLARR_TORRENT_CLIENT", ""))
api_key = config_text(config.get("ALLDEBRID_API_KEY", ""))
return client == "alldebrid" and bool(api_key)
def test_connection(self) -> tuple[bool, str]:
"""Validate the API key and check Premium subscription status."""
if not self._api_key:
return False, "AllDebrid API Key is required"
try:
url = f"{_API_BASE}/user"
resp = requests.get(
url,
headers=self._auth_headers(),
timeout=_STATUS_TIMEOUT,
verify=get_ssl_verify(url),
)
resp.raise_for_status()
data = resp.json()
if data.get("status") != "success":
err = data.get("error", {}).get("message", "API error")
return False, f"AllDebrid error: {err}"
user = data.get("data", {}).get("user", {})
username = user.get("username", "Unknown")
if not user.get("isPremium", False):
return (
False,
f"AllDebrid user '{username}' does not have a Premium subscription",
)
except _ALLDEBRID_CLIENT_ERRORS as e:
return False, f"Connection failed: {e}"
else:
return True, f"Connected to AllDebrid as '{username}' (Premium)"
def add_download(
self,
url: str,
name: str,
category: str | None = None,
expected_hash: str | None = None,
**kwargs: object,
) -> str:
"""Send a torrent to AllDebrid and return the magnet ID.
Accepts a magnet link, a .torrent URL, or an indexer proxy URL; anything
that is not already a magnet is resolved first, since an HTTP URL posted
as a magnet is rejected rather than downloaded (#1250).
"""
if not self._api_key:
msg = "AllDebrid API key is not configured"
raise RuntimeError(msg)
try:
upload = resolve_debrid_upload(url, expected_hash=expected_hash)
info = self._send_torrent(upload)
magnet_id = str(info.get("id", ""))
if not magnet_id:
msg = "No magnet ID returned from AllDebrid"
_raise_runtime_error(msg)
target_dir = TMP_DIR / f"alldebrid_{magnet_id}"
target_dir.mkdir(parents=True, exist_ok=True)
state = _DownloadState(
magnet_id=magnet_id,
name=name,
target_dir=target_dir,
phase="waiting_ad",
)
with self._downloads_lock:
self._downloads[magnet_id] = state
logger.info(
"Added torrent to AllDebrid: ID %s (%s)",
magnet_id,
name,
)
except Exception:
logger.exception("Failed to add torrent to AllDebrid")
raise
else:
return magnet_id
def _send_torrent(self, upload: DebridUpload) -> dict[str, Any]:
"""Hand the torrent to AllDebrid, as a magnet or as a file upload.
Both endpoints answer with the same envelope and the same per-entry
error shape, differing only in which key holds the entries.
"""
if isinstance(upload, DebridMagnet):
api_url = f"{_API_BASE}/magnet/upload"
entries_key = "magnets"
resp = requests.post(
api_url,
headers=self._auth_headers(),
data={"magnets[]": upload.magnet_url},
timeout=_API_TIMEOUT,
verify=get_ssl_verify(api_url),
)
else:
api_url = f"{_API_BASE}/magnet/upload/file"
entries_key = "files"
resp = requests.post(
api_url,
headers=self._auth_headers(),
files={
"files[]": (
"release.torrent",
upload.torrent_data,
"application/x-bittorrent",
)
},
timeout=_API_TIMEOUT,
verify=get_ssl_verify(api_url),
)
resp.raise_for_status()
data = resp.json()
if data.get("status") != "success":
code = data.get("error", {}).get("code", "UNKNOWN")
msg = f"AllDebrid upload failed: {code}"
_raise_runtime_error(msg)
entries = data.get("data", {}).get(entries_key, [])
if not entries:
msg = "AllDebrid accepted the upload but returned no torrent"
_raise_runtime_error(msg)
info = entries[0]
if info.get("error"):
code = info["error"].get("code", "UNKNOWN")
msg = f"AllDebrid rejected the torrent: {code}"
_raise_runtime_error(msg)
return info
def get_status(self, download_id: str) -> DownloadStatus:
"""Poll AllDebrid for magnet status and drive the download."""
state = self._ensure_state(download_id)
# Return cached terminal / in-flight states immediately.
with state.lock:
if state.phase == "error":
return DownloadStatus.error(
state.error_message or "AllDebrid error",
)
if state.phase == "complete":
return DownloadStatus(
progress=100.0,
state=DownloadState.COMPLETE,
message="Complete",
complete=True,
file_path=str(state.target_dir),
)
if state.phase == "downloading_http":
return DownloadStatus(
progress=state.progress,
state=DownloadState.DOWNLOADING,
message="Downloading files via HTTP...",
complete=False,
file_path=None,
)
# Ask AllDebrid for the current magnet status.
try:
status_url = f"{_API_BASE.replace('/v4', '/v4.1')}/magnet/status"
resp = requests.post(
status_url,
headers=self._auth_headers(),
data={"id": download_id},
timeout=_STATUS_TIMEOUT,
verify=get_ssl_verify(status_url),
)
resp.raise_for_status()
data = resp.json()
if data.get("status") != "success":
err = data.get("error", {}).get("message", "Status failed")
return DownloadStatus.error(
f"AllDebrid status error: {err}",
)
mag = self._extract_magnet_info(data)
return self._handle_magnet_status(mag, state)
except Exception as e:
logger.exception(
"Error checking AllDebrid status for %s",
download_id,
)
return DownloadStatus.error(str(e))
def remove(
self,
download_id: str,
*,
delete_files: bool = False,
) -> bool:
"""Delete the magnet from AllDebrid and clean up local files."""
try:
url = f"{_API_BASE}/magnet/delete"
requests.post(
url,
headers=self._auth_headers(),
data={"id": download_id},
timeout=_STATUS_TIMEOUT,
verify=get_ssl_verify(url),
)
except _ALLDEBRID_CLIENT_ERRORS as e:
logger.warning("Failed to delete magnet from AllDebrid: %s", e)
with self._downloads_lock:
state = self._downloads.pop(download_id, None)
if state and state.target_dir.exists():
shutil.rmtree(state.target_dir, ignore_errors=True)
return True
def get_download_path(self, download_id: str) -> str | None:
"""Return the local directory containing downloaded files."""
with self._downloads_lock:
state = self._downloads.get(download_id)
if state and state.phase == "complete":
return str(state.target_dir)
target_dir = TMP_DIR / f"alldebrid_{download_id}"
if target_dir.exists():
return str(target_dir)
return None
# ------------------------------------------------------------------
# Internal helpers
# ------------------------------------------------------------------
def _ensure_state(self, download_id: str) -> _DownloadState:
"""Get or create download state for the given magnet ID."""
with self._downloads_lock:
state = self._downloads.get(download_id)
if state:
return state
target_dir = TMP_DIR / f"alldebrid_{download_id}"
state = _DownloadState(
magnet_id=download_id,
name=f"Download {download_id}",
target_dir=target_dir,
phase="waiting_ad",
)
with self._downloads_lock:
self._downloads[download_id] = state
return state
@staticmethod
def _extract_magnet_info(data: dict[str, Any]) -> dict[str, Any]:
"""Extract magnet info dict from a status API response."""
mag_data = data.get("data", {}).get("magnets", {})
if isinstance(mag_data, list) and mag_data:
return mag_data[0]
if isinstance(mag_data, dict):
return mag_data
return {}
def _handle_magnet_status(
self,
mag: dict[str, Any],
state: _DownloadState,
) -> DownloadStatus:
"""Map AllDebrid magnet status to a DownloadStatus."""
status_code = mag.get("statusCode")
if status_code in _STATUS_DOWNLOADING:
size = mag.get("size", 0)
downloaded = mag.get("downloaded", 0)
pct = (downloaded / size * 100.0) if size > 0 else 0.0
return DownloadStatus(
progress=pct * 0.5,
state=DownloadState.DOWNLOADING,
message=(f"AllDebrid downloading torrent ({mag.get('filename', state.name)})"),
complete=False,
file_path=None,
download_speed=mag.get("downloadSpeed", 0),
)
if status_code == _STATUS_READY or mag.get("ready", False):
self._maybe_start_download_thread(state)
return DownloadStatus(
progress=50.0,
state=DownloadState.DOWNLOADING,
message="AllDebrid ready, retrieving files...",
complete=False,
file_path=None,
)
# Terminal error from AllDebrid.
error_txt = mag.get("error", {}).get("message") or f"AllDebrid status code {status_code}"
with state.lock:
state.phase = "error"
state.error_message = error_txt
return DownloadStatus.error(error_txt)
def _maybe_start_download_thread(self, state: _DownloadState) -> None:
"""Spawn a background thread to unlock and download files."""
with state.lock:
already_running = state.phase in (
"unlocking",
"downloading_http",
"complete",
)
thread_alive = state.download_thread is not None and state.download_thread.is_alive()
if already_running or thread_alive:
return
state.phase = "unlocking"
t = threading.Thread(
target=self._process_and_download,
args=(state,),
daemon=True,
)
state.download_thread = t
t.start()
# ------------------------------------------------------------------
# Link unlocking
# ------------------------------------------------------------------
def _unlock_file_link(self, link: str) -> str:
"""Resolve an AllDebrid file link to a direct CDN download URL.
AllDebrid's ``/v4/magnet/files`` endpoint returns virtual links
(``alldebrid.com/f/...``) that must be converted to direct CDN
URLs via ``/v4/link/unlock``.
Strategy:
1. If the link is already a CDN URL (``/dl/``), return it.
2. ``POST /v4/link/unlock`` with Bearer auth (primary).
3. ``GET /v4/link/unlock`` with query parameters (fallback).
4. Append ``apikey=`` to ``alldebrid.com/f/`` links
(last-resort fallback for ghost-cached torrents).
"""
# 1. Already a direct CDN link.
if "/dl/" in link:
return link
headers = self._auth_headers()
unlock_url = f"{_API_BASE}/link/unlock"
err_msg = "Unknown unlock error"
# 2. POST unlock (primary method).
try:
resp = requests.post(
unlock_url,
headers=headers,
data={"link": link},
timeout=_API_TIMEOUT,
verify=get_ssl_verify(unlock_url),
)
if resp.status_code == 200:
body = resp.json()
if body.get("status") == "success":
direct = self._resolve_unlock_data(
body.get("data", {}),
headers,
)
if direct:
return direct
err_msg = body.get("error", {}).get(
"message",
"Unlock failed",
)
except _ALLDEBRID_CLIENT_ERRORS as e:
logger.debug("POST unlock exception: %s", e)
# 3. GET unlock fallback with URL-encoded link.
try:
encoded = quote(link, safe="")
get_url = (
f"{_API_BASE}/link/unlock?agent={_AGENT}&apikey={self._api_key}&link={encoded}"
)
resp = requests.get(
get_url,
headers=headers,
timeout=_API_TIMEOUT,
verify=get_ssl_verify(get_url),
)
if resp.status_code == 200:
body = resp.json()
if body.get("status") == "success":
direct = body.get("data", {}).get("link")
if direct:
return direct
err_msg = body.get("error", {}).get("message", err_msg)
except _ALLDEBRID_CLIENT_ERRORS as e:
logger.debug("GET unlock exception: %s", e)
# 4. Last-resort: append apikey to alldebrid.com/f/ links.
if "alldebrid.com/f/" in link:
logger.info(
"Using apikey fallback for AllDebrid file link: %s",
link,
)
if "apikey=" not in link:
sep = "&" if "?" in link else "?"
return f"{link}{sep}apikey={self._api_key}"
return link
logger.error(
"AllDebrid unlock failed for '%s': %s",
link,
err_msg,
)
msg = f"AllDebrid unlock failed: {err_msg}"
raise RuntimeError(msg)
def _resolve_unlock_data(
self,
data: dict[str, Any],
headers: dict[str, str],
) -> str | None:
"""Extract the direct link from unlock response data.
Handles the *delayed link* flow where AllDebrid returns a
``delayed`` ID instead of an immediate download link.
"""
# Delayed link: poll until the CDN file is ready.
if "delayed" in data:
delayed_id = data["delayed"]
logger.info(
"AllDebrid link delayed (ID %s), polling...",
delayed_id,
)
delayed_url = f"{_API_BASE}/link/delayed"
for _ in range(_DELAYED_POLL_MAX_ATTEMPTS):
time.sleep(_DELAYED_POLL_INTERVAL)
try:
resp = requests.post(
delayed_url,
headers=headers,
data={"id": delayed_id},
timeout=_STATUS_TIMEOUT,
verify=get_ssl_verify(delayed_url),
)
if resp.status_code != 200:
continue
body = resp.json()
d = body.get("data", {})
if body.get("status") == "success" and d.get("status") == 2 and d.get("link"):
return d["link"]
except _ALLDEBRID_CLIENT_ERRORS as e:
logger.debug("Delayed poll exception: %s", e)
return data.get("link")
# ------------------------------------------------------------------
# File download pipeline
# ------------------------------------------------------------------
def _process_and_download(self, state: _DownloadState) -> None:
"""Fetch the file list, unlock links, and download via HTTP.
Runs in a background thread spawned by ``_maybe_start_download_thread``.
"""
try:
files = self._fetch_file_list(state.magnet_id)
relevant = [f for f in files if f["filename"].lower().endswith(_BOOK_EXTENSIONS)]
if not relevant:
relevant = files
with state.lock:
state.phase = "downloading_http"
total = len(relevant)
for idx, file_info in enumerate(relevant):
direct_link = self._unlock_file_link(file_info["link"])
rel_path = Path(file_info["filename"])
dest = state.target_dir / rel_path
dest.parent.mkdir(parents=True, exist_ok=True)
logger.info(
"Downloading AllDebrid file %d/%d: %s",
idx + 1,
total,
rel_path,
)
buf = download_url(
direct_link,
referer="https://alldebrid.com/",
)
if not buf:
msg = f"Failed to download from {direct_link}"
_raise_runtime_error(msg)
with dest.open("wb") as fh:
fh.write(buf.getvalue())
with state.lock:
state.progress = 50.0 + (idx + 1) / total * 50.0
with state.lock:
state.phase = "complete"
state.progress = 100.0
logger.info(
"AllDebrid download complete for ID %s at %s",
state.magnet_id,
state.target_dir,
)
except Exception:
logger.exception(
"Error in AllDebrid download for ID %s",
state.magnet_id,
)
with state.lock:
state.phase = "error"
state.error_message = str(
state.error_message or "Download failed",
)
def _fetch_file_list(
self,
magnet_id: str,
) -> list[dict[str, Any]]:
"""Retrieve and flatten the file tree for a magnet."""
url = f"{_API_BASE}/magnet/files"
resp = requests.post(
url,
headers=self._auth_headers(),
data={"id[]": magnet_id},
timeout=_API_TIMEOUT,
verify=get_ssl_verify(url),
)
resp.raise_for_status()
data = resp.json()
if data.get("status") != "success":
msg = f"Failed to list magnet files: {data.get('error')}"
raise RuntimeError(msg)
magnets = data.get("data", {}).get("magnets", [])
if not magnets:
msg = "No magnet files returned"
raise RuntimeError(msg)
files = _flatten_magnet_files(magnets[0].get("files", []))
if not files:
msg = "No files found in torrent"
raise RuntimeError(msg)
return files
+212 -32
View File
@@ -3,6 +3,7 @@
from __future__ import annotations
import errno
import math
import shutil
import time
from abc import ABC, abstractmethod
@@ -23,7 +24,7 @@ from shelfmark.download.clients import (
)
from shelfmark.download.fs import run_blocking_io
from shelfmark.download.permissions_debug import log_path_permission_context
from shelfmark.release_sources import DownloadHandler
from shelfmark.release_sources import DownloadHandler, HandoffResult
if TYPE_CHECKING:
from collections.abc import Callable
@@ -55,6 +56,19 @@ SECONDS_PER_HOUR = 3600
# How long to wait for completed files to appear (seconds)
COMPLETED_PATH_RETRY_INTERVAL = 5
COMPLETED_PATH_MAX_ATTEMPTS = 12 # 12 attempts * 5s = 60s grace period
COMPLETED_PATH_TIMEOUT_SETTING = "DOWNLOAD_CLIENT_COMPLETED_PATH_TIMEOUT"
COMPLETED_PATH_TIMEOUT_MAX_SECONDS = 3600
_RETRYABLE_COMPLETED_PATH_ERRNOS = frozenset(
code
for code in (
errno.ENOENT,
getattr(errno, "ESTALE", None),
getattr(errno, "EAGAIN", None),
getattr(errno, "EBUSY", None),
getattr(errno, "ETIMEDOUT", None),
)
if code is not None
)
@dataclass(frozen=True)
@@ -69,6 +83,39 @@ class DownloadRequest:
ratio_limit: float | None = None
@dataclass(frozen=True)
class _CompletedPathResolution:
path: Path | None
error: str | None
retryable: bool
def _coerce_completed_path_timeout_seconds(value: object, default: float) -> float:
if isinstance(value, bool) or value is None:
return default
if isinstance(value, (int, float)):
parsed = float(value)
elif isinstance(value, str):
try:
parsed = float(value.strip())
except ValueError:
return default
else:
return default
if not math.isfinite(parsed) or parsed < 0:
return default
return min(parsed, float(COMPLETED_PATH_TIMEOUT_MAX_SECONDS))
def _is_retryable_completed_path_probe(error: OSError | None) -> bool:
return error is not None and error.errno in _RETRYABLE_COMPLETED_PATH_ERRNOS
def _path_needs_mapping(path: str) -> bool:
return (len(path) >= WINDOWS_DRIVE_PREFIX_LENGTH and path[1] == ":") or "\\" in path
def _diagnose_path_issue(path: str) -> str:
"""Analyze a path and return diagnostic hints for common issues.
@@ -167,6 +214,23 @@ class ExternalClientHandler(DownloadHandler, ABC):
"""Maximum attempts when waiting for completed files."""
return COMPLETED_PATH_MAX_ATTEMPTS
def _completed_path_timeout_seconds(self) -> float:
"""Total time to wait for completed files to appear on disk."""
fallback = self._completed_path_retry_interval() * self._completed_path_max_attempts()
configured = config.get(COMPLETED_PATH_TIMEOUT_SETTING, fallback)
return _coerce_completed_path_timeout_seconds(configured, fallback)
def _refresh_download_request_after_add_failure(
self,
*,
task: DownloadTask,
request: DownloadRequest,
error: Exception,
status_callback: Callable[[str, str | None], None],
) -> DownloadRequest | None:
"""Give source handlers one chance to refresh stale resolved download data."""
return None
def _get_category_for_task(self, client: DownloadClient, task: DownloadTask) -> str | None:
"""Get audiobook category if configured and applicable, else None for default."""
if not is_audiobook(task.content_type):
@@ -220,17 +284,47 @@ class ExternalClientHandler(DownloadHandler, ABC):
)
elif protocol == "torrent":
if config.get("PROWLARR_TORRENT_ACTION", "keep") != "remove":
torrent_action = config.get("PROWLARR_TORRENT_ACTION", "keep")
if torrent_action == "remove":
try:
client.remove(download_id, delete_files=False)
except _CLIENT_CLEANUP_ERRORS as e:
logger.warning(
"Failed to remove torrent %s from %s: %s",
download_id,
getattr(client, "name", "client"),
e,
)
return
if torrent_action != "change_category":
return
post_import_category = normalize_optional_text(
config.get("PROWLARR_TORRENT_POST_IMPORT_CATEGORY", "")
)
if post_import_category is None:
return
try:
client.remove(download_id, delete_files=False)
category_updated = client.set_category(download_id, post_import_category)
except _CLIENT_CLEANUP_ERRORS as e:
logger.warning(
"Failed to remove torrent %s from %s: %s",
"Failed to set post-import category for torrent %s in %s: %s",
download_id,
getattr(client, "name", "client"),
e,
)
return
if not category_updated:
# Clients that cannot label torrents (debrid services) return False here,
# and the ones that can already log the specific failure themselves.
logger.debug(
"Post-import category not applied to torrent %s in %s",
download_id,
getattr(client, "name", "client"),
)
def _remove_usenet_download(
self,
@@ -387,6 +481,21 @@ class ExternalClientHandler(DownloadHandler, ABC):
log_details: bool,
) -> tuple[Path | None, str | None]:
"""Resolve and validate the completed download path once."""
result = self._resolve_download_path_once_detailed(
client,
download_id,
log_details=log_details,
)
return result.path, result.error
def _resolve_download_path_once_detailed(
self,
client: DownloadClient,
download_id: str,
*,
log_details: bool,
) -> _CompletedPathResolution:
"""Resolve and validate a completed path, including retryability."""
try:
raw_path = client.get_download_path(download_id)
except Exception as e:
@@ -402,7 +511,7 @@ class ExternalClientHandler(DownloadHandler, ABC):
logger.debug(
"Failed to resolve download path for %s %s: %s", client.name, download_id, e
)
return None, message
return _CompletedPathResolution(None, message, retryable=False)
if not raw_path:
message = (
@@ -417,7 +526,7 @@ class ExternalClientHandler(DownloadHandler, ABC):
logger.debug(
"Download client returned empty path for %s %s", client.name, download_id
)
return None, message
return _CompletedPathResolution(None, message, retryable=False)
from shelfmark.core.path_mappings import (
get_client_host_identifier,
@@ -457,7 +566,7 @@ class ExternalClientHandler(DownloadHandler, ABC):
logger.error(failure_log, *failure_args)
else:
logger.debug(failure_log, *failure_args)
return None, message
return _CompletedPathResolution(None, message, retryable=False)
remapped_exists, remapped_error = _probe_completed_path(remapped)
@@ -480,7 +589,7 @@ class ExternalClientHandler(DownloadHandler, ABC):
source_path_obj,
remapped,
)
return remapped, None
return _CompletedPathResolution(remapped, None, retryable=False)
message = (
f"Remapped path '{remapped}' does not exist. "
@@ -499,7 +608,11 @@ class ExternalClientHandler(DownloadHandler, ABC):
logger.error(failure_log, *failure_args)
else:
logger.debug(failure_log, *failure_args)
return None, message
return _CompletedPathResolution(
None,
message,
retryable=_is_retryable_completed_path_probe(remapped_error),
)
source_exists, source_error = _probe_completed_path(source_path_obj)
@@ -522,7 +635,7 @@ class ExternalClientHandler(DownloadHandler, ABC):
download_id,
source_path_obj,
)
return source_path_obj, None
return _CompletedPathResolution(source_path_obj, None, retryable=False)
hint = _diagnose_path_issue(raw_path)
if mappings:
@@ -555,7 +668,12 @@ class ExternalClientHandler(DownloadHandler, ABC):
logger.error(failure_log, *failure_args)
else:
logger.debug(failure_log, *failure_args)
return None, message
return _CompletedPathResolution(
None,
message,
retryable=not _path_needs_mapping(raw_path)
and _is_retryable_completed_path_probe(source_error),
)
def _wait_for_completed_path(
self,
@@ -567,23 +685,37 @@ class ExternalClientHandler(DownloadHandler, ABC):
) -> tuple[Path | None, str | None]:
"""Wait briefly for completed files to appear on disk."""
last_error: str | None = None
max_attempts = self._completed_path_max_attempts()
retry_interval = self._completed_path_retry_interval()
timeout_seconds = self._completed_path_timeout_seconds()
if retry_interval <= 0 or timeout_seconds <= 0:
max_attempts = 1
else:
max_attempts = int(math.ceil(timeout_seconds / retry_interval)) + 1
for attempt in range(1, max_attempts + 1):
if cancel_flag and cancel_flag.is_set():
return None, last_error
log_details = attempt == max_attempts
resolved_path, error = self._resolve_download_path_once(
result = self._resolve_download_path_once_detailed(
client,
download_id,
log_details=log_details,
)
if resolved_path:
return resolved_path, None
if result.path:
return result.path, None
last_error = error
last_error = result.error
if not result.retryable:
if not log_details:
logger.error(
"Completed path resolution is not retryable for %s (%s): %s",
client.name,
download_id,
last_error,
)
return None, last_error
if attempt < max_attempts:
status_callback("locating", "Waiting for completed files...")
@@ -630,8 +762,8 @@ class ExternalClientHandler(DownloadHandler, ABC):
cancel_flag: Event,
progress_callback: Callable[[float], None],
status_callback: Callable[[str, str | None], None],
) -> str | None:
"""Execute download via configured torrent/usenet client. Returns file path or None."""
) -> str | HandoffResult | None:
"""Execute download via configured torrent/usenet client."""
try:
if cancel_flag.is_set():
status_callback("cancelled", "Cancelled")
@@ -700,24 +832,72 @@ class ExternalClientHandler(DownloadHandler, ABC):
status_callback("downloading", "Resuming existing download")
else:
# No existing download - add new
status_callback("resolving", f"Sending to {client.name}")
try:
download_id = client.add_download(
url=request.url,
name=request.release_name,
category=category,
expected_hash=request.expected_hash,
seeding_time_limit=request.seeding_time_limit,
ratio_limit=request.ratio_limit,
)
except Exception as e:
logger.exception("Failed to add to %s", client.name)
status_callback("error", f"Failed to add to {client.name}: {e}")
return None
refresh_attempted = False
while True:
status_callback("resolving", f"Sending to {client.name}")
try:
download_id = client.add_download(
url=request.url,
name=request.release_name,
category=category,
expected_hash=request.expected_hash,
seeding_time_limit=request.seeding_time_limit,
ratio_limit=request.ratio_limit,
# rTorrent has no category concept, so its audiobook label
# can only be chosen from the content type (#1235).
content_type=task.content_type,
)
except Exception as e:
if not refresh_attempted:
refresh_attempted = True
refreshed_request = self._refresh_download_request_after_add_failure(
task=task,
request=request,
error=e,
status_callback=status_callback,
)
if (
refreshed_request is not None
and refreshed_request.protocol == request.protocol
):
request = refreshed_request
continue
logger.exception("Failed to add to %s", client.name)
status_callback("error", f"Failed to add to {client.name}: {e}")
return None
break
logger.info(
"Added to %s: %s for '%s'", client.name, download_id, request.release_name
)
if getattr(client, "handoff_only", False) is True:
if cancel_flag.is_set():
# The file is published and unpublishing it would race a watcher
# that may already have consumed it, so the handoff stands even
# though the task is cancelled. Say so rather than leaving a bare
# "Cancelled" the user cannot act on.
logger.info(
"Cancelled after handoff to %s; leaving publication in place: %s",
client.name,
download_id,
)
status_callback(
"cancelled",
f"Cancelled, but the torrent was already handed off to {client.name}",
)
return None
# A watcher can consume the publication as soon as add_download returns,
# so the handoff completes here rather than in the poll loop. The
# orchestrator deliberately does not check that this path still exists:
# a consumed publication is indistinguishable from a bogus one, and
# treating it as an error is the failure this avoids (#1345).
progress_callback(100)
self._on_download_complete(task)
return HandoffResult(
path=download_id,
message=f"Torrent file saved to {download_id}",
)
# Poll for progress
return self._poll_and_complete(
+113
View File
@@ -0,0 +1,113 @@
"""Blackhole download client that saves torrent files for an external watcher."""
from __future__ import annotations
import os
import tempfile
from pathlib import Path
from shelfmark.core.config import config
from shelfmark.core.naming import sanitize_filename
from shelfmark.download.clients import (
DownloadClient,
DownloadState,
DownloadStatus,
register_client,
)
from shelfmark.download.clients._coercion import config_text
from shelfmark.download.clients.torrent_utils import extract_torrent_info
@register_client("torrent")
class BlackholeClient(DownloadClient):
"""Write fetched torrent files to a directory watched by another downloader."""
protocol = "torrent"
name = "blackhole"
handoff_only = True
prefers_torrent_file = True
def __init__(self) -> None:
directory = config_text(config.get("BLACKHOLE_DIRECTORY", ""))
if not directory:
msg = "BLACKHOLE_DIRECTORY is required"
raise ValueError(msg)
self._directory = Path(directory)
@staticmethod
def is_configured() -> bool:
return config_text(config.get("PROWLARR_TORRENT_CLIENT", "")) == "blackhole" and bool(
config_text(config.get("BLACKHOLE_DIRECTORY", ""))
)
def test_connection(self) -> tuple[bool, str]:
try:
self._directory.mkdir(parents=True, exist_ok=True)
except OSError as error:
return False, f"Could not create Blackhole directory: {error}"
return True, f"Blackhole directory is ready: {self._directory}"
def add_download(
self,
url: str,
name: str,
category: str | None = None,
expected_hash: str | None = None,
**kwargs: object,
) -> str:
torrent_info = extract_torrent_info(url, expected_hash=expected_hash)
if not torrent_info.torrent_data:
msg = "Blackhole requires a .torrent file; this release only provides a magnet link"
raise ValueError(msg)
self._directory.mkdir(parents=True, exist_ok=True)
filename = f"{sanitize_filename(name) or 'torrent'}.torrent"
destination = self._next_destination(filename)
file_descriptor, temporary_path = tempfile.mkstemp(
dir=self._directory,
prefix=".blackhole-",
suffix=".tmp",
)
try:
with os.fdopen(file_descriptor, "wb") as temporary_file:
temporary_file.write(torrent_info.torrent_data)
temporary_file.flush()
os.fsync(temporary_file.fileno())
Path(temporary_path).replace(destination)
except Exception:
Path(temporary_path).unlink(missing_ok=True)
raise
return str(destination)
def get_status(self, download_id: str) -> DownloadStatus:
file_path = Path(download_id)
if file_path.is_file():
return DownloadStatus(
progress=100,
state=DownloadState.COMPLETE,
message="Torrent file saved",
complete=True,
file_path=str(file_path),
)
return DownloadStatus.error("Blackhole torrent file was not created")
def remove(self, download_id: str, *, delete_files: bool = False) -> bool:
return False
def get_download_path(self, download_id: str) -> str | None:
return download_id if Path(download_id).is_file() else None
def _next_destination(self, filename: str) -> Path:
candidate = self._directory / filename
if not candidate.exists():
return candidate
stem = Path(filename).stem
suffix = Path(filename).suffix
index = 1
while True:
candidate = self._directory / f"{stem}_{index}{suffix}"
if not candidate.exists():
return candidate
index += 1
+24 -6
View File
@@ -227,10 +227,10 @@ class DelugeClient(DownloadClient):
return self._rpc_call("daemon.info")
def _try_set_label(self, torrent_id: str, label: str) -> None:
def _try_set_label(self, torrent_id: str, label: str) -> bool:
"""Best-effort label assignment (requires Deluge Label plugin)."""
if not label:
return
return False
try:
# label.add will error if the plugin is unavailable or the label exists.
@@ -240,6 +240,9 @@ class DelugeClient(DownloadClient):
self._rpc_call("label.set_torrent", torrent_id, label)
except _DELUGE_CLIENT_ERRORS as e:
logger.debug("Could not set Deluge label '%s' for %s: %s", label, torrent_id, e)
return False
else:
return True
@staticmethod
def is_configured() -> bool:
@@ -277,7 +280,10 @@ class DelugeClient(DownloadClient):
torrent_info = extract_torrent_info(url, expected_hash=expected_hash)
if not torrent_info.is_magnet and not torrent_info.torrent_data:
_raise_runtime_error("Failed to fetch torrent file")
message = "Failed to fetch torrent file"
if torrent_info.fetch_error:
message = f"{message}: {torrent_info.fetch_error}"
_raise_runtime_error(message)
options: dict[str, Any] = {}
if self._download_dir:
@@ -286,11 +292,14 @@ class DelugeClient(DownloadClient):
# Per-torrent seeding limits from indexer
seeding_time_limit = coerce_optional_int(kwargs.get("seeding_time_limit"))
if seeding_time_limit is not None:
options["seed_time_limit"] = seeding_time_limit
logger.debug(
"Deluge has no per-torrent seeding time limit, ignoring %s minutes",
seeding_time_limit,
)
ratio_limit = coerce_optional_float(kwargs.get("ratio_limit"))
if ratio_limit is not None:
options["stop_at_ratio"] = ratio_limit
options["stop_at_ratio_enabled"] = True
options["stop_ratio"] = ratio_limit
options["stop_at_ratio"] = True
if torrent_info.is_magnet:
magnet_url = torrent_info.magnet_url or url
@@ -419,6 +428,15 @@ class DelugeClient(DownloadClient):
else:
return False
def set_category(self, download_id: str, category: str) -> bool:
"""Assign a label to a torrent using Deluge's Label plugin."""
try:
self._ensure_connected()
return self._try_set_label(download_id, category)
except _DELUGE_CLIENT_ERRORS as e:
self._log_error("set_category", e)
return False
def get_download_path(self, download_id: str) -> str | None:
"""Return the resolved download path for a Deluge torrent."""
try:
+277 -180
View File
@@ -44,6 +44,11 @@ _HASH_LENGTH_40 = 40
_HASH_LENGTH_ED2K = 32
_HTTP_STATUS_FORBIDDEN = HTTPStatus.FORBIDDEN
_HTTP_STATUS_NOT_FOUND = HTTPStatus.NOT_FOUND
_METADATA_DOWNLOAD_STATES = {"forcedMetaDL", "metaDL"}
# How long add_download waits for magnet metadata before falling back to the info
# hash it already knows, rather than holding the download queue on a thin swarm.
_METADATA_WAIT_POLLS = 20
_METADATA_WAIT_INTERVAL_SECONDS = 0.5
_ONE_WEEK_IN_SECONDS = 604800
@@ -94,6 +99,25 @@ def _hashes_match(hash1: str, hash2: str) -> bool:
return False
def _torrent_matches_download_id(torrent: object, download_id: str) -> bool:
"""Match an ID against every identity qBittorrent exposes.
For hybrid torrents, qBittorrent's primary `hash` can change from the v1
hash to the truncated v2 hash after metadata resolution. The full
`infohash_v1` and `infohash_v2` fields preserve the torrent's identities.
"""
identifiers = (
getattr(torrent, "hash", None),
getattr(torrent, "infohash_v1", None),
getattr(torrent, "infohash_v2", None),
)
return any(
isinstance(identifier, str) and identifier and _hashes_match(identifier, download_id)
for identifier in identifiers
)
def _raise_runtime_error(message: str) -> NoReturn:
raise RuntimeError(message)
@@ -166,63 +190,6 @@ def _build_qbittorrent_child_path(base_path: object, child_path: object) -> str
class QBittorrentClient(DownloadClient):
"""qBittorrent download client."""
def _is_torrent_loaded(self, torrent_hash: str) -> tuple[bool, str | None]:
"""Check whether qBittorrent has registered a torrent yet.
Uses `/api/v2/torrents/properties?hash=<hash>`.
Returns:
(loaded, error_message)
Notes:
A false result with no error means "not loaded yet".
"""
url = f"{self._base_url}/api/v2/torrents/properties"
params = {"hash": torrent_hash}
try:
self._client.auth_log_in()
response = self._client._session.get(url, params=params, timeout=10)
# Re-authenticate and retry once on 403
if response.status_code == _HTTP_STATUS_FORBIDDEN:
logger.debug(
"qBittorrent returned 403 for properties; re-authenticating and retrying"
)
self._client.auth_log_in()
response = self._client._session.get(url, params=params, timeout=10)
if response.status_code == _HTTP_STATUS_FORBIDDEN:
return False, "qBittorrent authentication failed (HTTP 403)"
# qBittorrent returns 404/409-ish responses depending on version when missing.
if response.status_code == _HTTP_STATUS_NOT_FOUND:
return False, None
response.raise_for_status()
except requests.exceptions.HTTPError as e:
status = getattr(getattr(e, "response", None), "status_code", None)
if status == _HTTP_STATUS_NOT_FOUND:
return False, None
if status:
return False, f"qBittorrent API request failed (HTTP {status})"
return False, "qBittorrent API request failed"
except requests.exceptions.ConnectionError:
return False, f"Cannot connect to qBittorrent at {self._base_url}"
except requests.exceptions.Timeout:
return False, f"qBittorrent request timed out at {self._base_url}"
except requests.exceptions.InvalidSchema:
return (
False,
"qBittorrent URL is invalid (missing http:// or https://). "
f"Configured: {self._base_url}",
)
except _QBITTORRENT_CLIENT_ERRORS as e:
return False, f"qBittorrent API error: {type(e).__name__}: {e}"
else:
return True, None
protocol = "torrent"
name = "qbittorrent"
@@ -244,6 +211,7 @@ class QBittorrentClient(DownloadClient):
username = config_text(config.get("QBITTORRENT_USERNAME", ""))
password = config_text(config.get("QBITTORRENT_PASSWORD", ""))
self._api_key = config_text(config.get("QBITTORRENT_API_KEY", ""))
# qbittorrent-api accepts either a full URL or host:port; prefer the normalized URL
# for consistency.
@@ -251,43 +219,43 @@ class QBittorrentClient(DownloadClient):
host=self._base_url,
username=username,
password=password,
api_key=self._api_key or None,
VERIFY_WEBUI_CERTIFICATE=get_ssl_verify(self._base_url),
)
self._category = config_text(config.get("QBITTORRENT_CATEGORY", "books"))
self._download_dir = config_text(config.get("QBITTORRENT_DOWNLOAD_DIR", ""))
self._tags = _normalize_tags(config.get("QBITTORRENT_TAG", []))
# download_id -> qBittorrent's current primary hash, for identities that no
# longer match it directly. See _resolve_torrent().
self._primary_hashes: dict[str, str] = {}
def _get_torrents_info(
self, torrent_hash: str | None = None
) -> tuple[list[SimpleNamespace], str | None]:
"""Get torrent info using GET.
@property
def _can_reauthenticate(self) -> bool:
"""Whether a 403 is worth retrying; a bearer token cannot be refreshed like a session."""
return not self._api_key
Behaviors:
- Retry once on HTTP 403 by re-authenticating.
- Keep "API/auth/connect" errors distinct from "torrent missing".
- If a hash-specific query returns empty, fall back to listing by category
and matching locally.
Returns:
(torrents, error_message)
def _ensure_authenticated(self) -> None:
"""Authenticate the underlying HTTP session before it is used directly.
API keys (qBittorrent 5.2.0+) are sent as a bearer header on every request and
have no login endpoint, so there is no session to establish up front.
"""
if self._api_key:
return
self._client.auth_log_in()
def _request_torrent_info_records(
self, params: dict[str, str]
) -> tuple[list[SimpleNamespace], str | None]:
"""Request torrent info records from qBittorrent."""
url = f"{self._base_url}/api/v2/torrents/info"
def do_request(params: dict[str, str]) -> requests.Response:
# Ensure session is authenticated before using it directly
self._client.auth_log_in()
return self._client._session.get(url, params=params, timeout=10)
def parse_response(
response: requests.Response,
*,
request_params: dict[str, str],
) -> tuple[list[SimpleNamespace], str | None]:
if response.status_code == _HTTP_STATUS_FORBIDDEN:
try:
self._ensure_authenticated()
response = self._client._session.get(url, params=params, timeout=10)
if response.status_code == _HTTP_STATUS_FORBIDDEN and self._can_reauthenticate:
logger.debug("qBittorrent returned 403; re-authenticating and retrying")
self._client.auth_log_in()
response = self._client._session.get(url, params=request_params, timeout=10)
self._ensure_authenticated()
response = self._client._session.get(url, params=params, timeout=10)
if response.status_code == _HTTP_STATUS_FORBIDDEN:
logger.warning("qBittorrent authentication failed (HTTP 403)")
@@ -296,41 +264,6 @@ class QBittorrentClient(DownloadClient):
response.raise_for_status()
torrents = response.json()
return [SimpleNamespace(**t) for t in torrents], None
try:
primary_params: dict[str, str] = {}
if torrent_hash:
primary_params["hashes"] = torrent_hash
response = do_request(primary_params)
torrents, error = parse_response(response, request_params=primary_params)
if error:
return [], error
if torrent_hash and not torrents:
# Fallback 1: list by configured category
category_params: dict[str, str] = {}
if self._category:
category_params["category"] = self._category
category_response = do_request(category_params)
category_torrents, category_error = parse_response(
category_response, request_params=category_params
)
if category_error:
return [], category_error
if category_torrents:
return category_torrents, None
# Fallback 2: list everything (handles per-task categories like audiobooks)
all_response = do_request({})
all_torrents, all_error = parse_response(all_response, request_params={})
if all_error:
return [], all_error
return all_torrents, None
except requests.exceptions.HTTPError as e:
status = getattr(getattr(e, "response", None), "status_code", None)
if status:
@@ -355,8 +288,141 @@ class QBittorrentClient(DownloadClient):
except _QBITTORRENT_CLIENT_ERRORS as e:
logger.debug("Failed to get torrents info: %s", e)
return [], f"qBittorrent API error: {type(e).__name__}: {e}"
def _get_torrent_info(self, download_id: str) -> tuple[SimpleNamespace | None, str | None]:
"""Get one torrent by its current qBittorrent hash."""
torrents, error = self._request_torrent_info_records({"hashes": download_id})
if error or not torrents:
return None, error
return (
next(
(
torrent
for torrent in torrents
if isinstance(getattr(torrent, "hash", None), str)
and _hashes_match(torrent.hash, download_id)
),
None,
),
None,
)
def _list_torrents_by_category(
self, category: str | None
) -> tuple[list[SimpleNamespace], str | None]:
"""List torrent records in a category, or all records when unset."""
params = {"category": category} if category else {}
return self._request_torrent_info_records(params)
def _remember_primary_hash(self, download_id: str, torrent: SimpleNamespace) -> None:
"""Note the primary hash a listing scan found, so later lookups skip the scan."""
torrent_hash = getattr(torrent, "hash", None)
if isinstance(torrent_hash, str) and torrent_hash:
self._primary_hashes[download_id.lower()] = torrent_hash.lower()
def _resolve_torrent(
self, download_id: str, category: str | None = None
) -> tuple[SimpleNamespace | None, str | None]:
"""Resolve any known torrent identity to its current qBittorrent record.
A hybrid torrent's primary hash switches from the v1 hash to the truncated v2
hash once metadata resolves, so a download tracked by its v1 hash misses the
`hashes=` lookup and falls through to a full listing. Since `get_status()`
polls every couple of seconds for the life of the download, remember the
primary hash a scan finds and try it first.
"""
cached = self._primary_hashes.get(download_id.lower())
for candidate in (item for item in dict.fromkeys((cached, download_id)) if item):
torrent, error = self._get_torrent_info(candidate)
if error:
return None, error
if torrent:
self._remember_primary_hash(download_id, torrent)
return torrent, None
categories = [candidate for candidate in (category, self._category) if candidate]
for candidate in dict.fromkeys(categories):
torrents, error = self._list_torrents_by_category(candidate)
if error:
return None, error
torrent = next(
(item for item in torrents if _torrent_matches_download_id(item, download_id)),
None,
)
if torrent:
self._remember_primary_hash(download_id, torrent)
return torrent, None
torrents, error = self._list_torrents_by_category(None)
if error:
return None, error
torrent = next(
(item for item in torrents if _torrent_matches_download_id(item, download_id)),
None,
)
if torrent:
self._remember_primary_hash(download_id, torrent)
else:
return torrents, None
# The torrent is gone; drop the note so a re-add is not looked up by a
# hash that no longer exists.
self._primary_hashes.pop(download_id.lower(), None)
return torrent, None
def _current_hash(self, download_id: str) -> str:
"""qBittorrent's current primary hash for any identity we know the torrent by.
Falls back to the given ID when the torrent cannot be found, so callers
still address the hash they were handed and surface the client's error.
"""
try:
torrent, error = self._resolve_torrent(download_id)
except _QBITTORRENT_CLIENT_ERRORS as e:
logger.debug("Could not resolve current hash for %s: %s", download_id, e)
return download_id
if error or not torrent:
return download_id
torrent_hash = getattr(torrent, "hash", None)
if isinstance(torrent_hash, str) and torrent_hash:
return torrent_hash
return download_id
def _list_category_hashes(self, category: str | None) -> set[str] | None:
"""Snapshot the hashes qBittorrent currently reports for a category."""
torrents, error = self._list_torrents_by_category(category)
if error:
logger.debug("Could not snapshot qBittorrent torrents: %s", error)
return None
return {str(torrent.hash).lower() for torrent in torrents if getattr(torrent, "hash", None)}
def _discover_added_torrent_hash(
self,
name: str,
category: str | None,
known_hashes: set[str] | None,
) -> str | None:
"""Recover the hash of a torrent that was added without a known info_hash.
A `known_hashes` of None means the pre-add snapshot failed, so only a
torrent matching the requested rename can identify the new arrival.
"""
for _ in range(20):
torrents, error = self._list_torrents_by_category(category)
if error:
logger.debug("qBittorrent hash discovery: %s", error)
else:
new_torrents = [
torrent
for torrent in torrents
if getattr(torrent, "hash", None)
and (known_hashes is None or str(torrent.hash).lower() not in known_hashes)
]
for torrent in new_torrents:
if getattr(torrent, "name", None) == name:
return str(torrent.hash).lower()
if known_hashes is not None and len(new_torrents) == 1:
return str(new_torrents[0].hash).lower()
time.sleep(0.5)
return None
@staticmethod
def is_configured() -> bool:
@@ -368,7 +434,7 @@ class QBittorrentClient(DownloadClient):
def test_connection(self) -> tuple[bool, str]:
"""Test connection to qBittorrent."""
try:
self._client.auth_log_in()
self._ensure_authenticated()
api_version = self._client.app.web_api_version
except _QBITTORRENT_CLIENT_ERRORS as e:
return False, f"Connection failed: {e!s}"
@@ -425,6 +491,10 @@ class QBittorrentClient(DownloadClient):
expected_hash = torrent_info.info_hash
torrent_data = torrent_info.torrent_data
known_hashes: set[str] | None = None
if not expected_hash:
known_hashes = self._list_category_hashes(category)
# Per-torrent seeding limits from indexer
seeding_time_limit_value = kwargs.get("seeding_time_limit")
seeding_time_limit = coerce_optional_int(seeding_time_limit_value)
@@ -459,33 +529,47 @@ class QBittorrentClient(DownloadClient):
result_text = _normalize_add_result(result)
logger.debug("qBittorrent add result: %s", result_text)
if not expected_hash:
_raise_runtime_error("Could not determine torrent hash from URL")
if _is_explicit_add_failure(result):
_raise_runtime_error(f"Failed to add torrent: {result_text}")
# Some qBittorrent-compatible clients return HTTP 200 with an empty body
# instead of qBittorrent's literal "Ok." response. Prefer verifying that
# the torrent becomes visible over trusting the response body alone.
for _ in range(10):
loaded, error = self._is_torrent_loaded(expected_hash)
if not expected_hash:
# qBittorrent fetches .torrent URLs itself, so the add can succeed
# even when no hash could be extracted up front. Recover it by
# watching for the new torrent to appear.
expected_hash = self._discover_added_torrent_hash(name, category, known_hashes)
if not expected_hash:
message = "Could not determine torrent hash from URL"
if torrent_info.fetch_error:
message = f"{message} (torrent file fetch failed: {torrent_info.fetch_error})"
_raise_runtime_error(message)
# Prefer qBittorrent's primary torrent ID, which for hybrid torrents
# switches from the v1 hash to the truncated v2 hash once metadata
# resolves. A magnet with few peers can take minutes to fetch metadata,
# and the torrent is worth keeping in the meantime: every lookup goes
# through `_resolve_torrent`, which still matches the v1 hash against
# `infohash_v1` after the primary ID has changed.
for _ in range(_METADATA_WAIT_POLLS):
torrent, error = self._resolve_torrent(expected_hash, category)
if error:
logger.debug("qBittorrent add_download: %s", error)
if loaded:
logger.info("Added torrent: %s", expected_hash)
return expected_hash.lower()
time.sleep(0.5)
elif torrent and getattr(torrent, "state", None) not in _METADATA_DOWNLOAD_STATES:
torrent_hash = getattr(torrent, "hash", None)
if isinstance(torrent_hash, str) and torrent_hash:
logger.info("Added torrent: %s", torrent_hash)
return torrent_hash.lower()
time.sleep(_METADATA_WAIT_INTERVAL_SECONDS)
logger.warning(
"Torrent add was not confirmed within the visibility grace period (response=%s), returning expected hash",
result_text,
logger.info(
"Added torrent %s; metadata still pending after %.0fs, tracking it by info hash",
expected_hash,
_METADATA_WAIT_POLLS * _METADATA_WAIT_INTERVAL_SECONDS,
)
except _QBITTORRENT_CLIENT_ERRORS:
logger.exception("qBittorrent add failed")
raise
else:
return expected_hash
return expected_hash.lower()
def get_status(self, download_id: str) -> DownloadStatus:
"""Get torrent status by hash.
@@ -498,19 +582,9 @@ class QBittorrentClient(DownloadClient):
"""
try:
torrents, error = self._get_torrents_info(download_id)
torrent, error = self._resolve_torrent(download_id)
if error:
return DownloadStatus.error(error)
torrent = next(
(
t
for t in torrents
if isinstance(getattr(t, "hash", None), str)
and _hashes_match(t.hash, download_id)
),
None,
)
if not torrent:
return DownloadStatus.error("Torrent not found in qBittorrent")
@@ -592,7 +666,9 @@ class QBittorrentClient(DownloadClient):
"""
try:
self._client.torrents_delete(torrent_hashes=download_id, delete_files=delete_files)
torrent_hash = self._current_hash(download_id)
self._client.torrents_delete(torrent_hashes=torrent_hash, delete_files=delete_files)
self._primary_hashes.pop(download_id.lower(), None)
logger.info(
"Removed torrent from qBittorrent: %s%s",
download_id,
@@ -604,6 +680,26 @@ class QBittorrentClient(DownloadClient):
else:
return True
def set_category(self, download_id: str, category: str) -> bool:
"""Assign a category to a torrent in qBittorrent."""
try:
try:
self._client.torrents_create_category(name=category)
except _QBITTORRENT_CLIENT_ERRORS as e:
if "Conflict" not in type(e).__name__ and "409" not in str(e):
logger.debug("Could not create category '%s': %s", category, e)
self._client.torrents_set_category(
torrent_hashes=self._current_hash(download_id),
category=category,
)
logger.info("Set qBittorrent category for %s to '%s'", download_id, category)
except _QBITTORRENT_CLIENT_ERRORS as e:
self._log_error("set_category", e)
return False
else:
return True
def get_download_path(self, download_id: str) -> str | None:
"""Get the path where torrent files are located.
@@ -616,20 +712,10 @@ class QBittorrentClient(DownloadClient):
- join `save_path` with the torrent's top-level directory
"""
try:
torrents, error = self._get_torrents_info(download_id)
torrent, error = self._resolve_torrent(download_id)
if error:
logger.debug("qBittorrent get_download_path: %s", error)
return None
torrent = next(
(
t
for t in torrents
if isinstance(getattr(t, "hash", None), str)
and _hashes_match(t.hash, download_id)
),
None,
)
if not torrent:
return None
@@ -675,11 +761,11 @@ class QBittorrentClient(DownloadClient):
import os
def get_with_auth(url: str, params: dict[str, str]) -> requests.Response:
self._client.auth_log_in()
self._ensure_authenticated()
resp = self._client._session.get(url, params=params, timeout=10)
if resp.status_code == _HTTP_STATUS_FORBIDDEN:
if resp.status_code == _HTTP_STATUS_FORBIDDEN and self._can_reauthenticate:
logger.debug("qBittorrent returned 403; re-authenticating and retrying")
self._client.auth_log_in()
self._ensure_authenticated()
resp = self._client._session.get(url, params=params, timeout=10)
return resp
@@ -727,6 +813,33 @@ class QBittorrentClient(DownloadClient):
)
return None
def _await_existing_torrent(
self, info_hash: str, category: str | None
) -> tuple[str, DownloadStatus] | None:
"""Report a torrent already in qBittorrent, waiting out magnet metadata first."""
for _ in range(_METADATA_WAIT_POLLS):
torrent, error = self._resolve_torrent(info_hash, category)
if error:
logger.debug("qBittorrent find_existing: %s", error)
return None
if not torrent:
return None
if getattr(torrent, "state", None) not in _METADATA_DOWNLOAD_STATES:
torrent_hash = getattr(torrent, "hash", None)
if isinstance(torrent_hash, str) and torrent_hash:
torrent_hash = torrent_hash.lower()
return (torrent_hash, self.get_status(torrent_hash))
time.sleep(_METADATA_WAIT_INTERVAL_SECONDS)
# Metadata is still pending, but the torrent is here and `add_download` keeps
# one in this state rather than giving up. Report it by info hash so the
# caller joins the download in progress instead of adding a duplicate.
logger.info(
"Existing torrent %s is still fetching metadata; joining it by info hash",
info_hash,
)
return (info_hash.lower(), self.get_status(info_hash))
def find_existing(
self, url: str, category: str | None = None
) -> tuple[str, DownloadStatus] | None:
@@ -736,25 +849,9 @@ class QBittorrentClient(DownloadClient):
if not torrent_info.info_hash:
return None
torrents, error = self._get_torrents_info(torrent_info.info_hash)
if error:
logger.debug("qBittorrent find_existing: %s", error)
return None
torrent = next(
(
t
for t in torrents
if isinstance(getattr(t, "hash", None), str)
and _hashes_match(t.hash, torrent_info.info_hash)
),
None,
)
if torrent and isinstance(getattr(torrent, "hash", None), str):
torrent_hash = torrent.hash
return (torrent_hash.lower(), self.get_status(torrent_hash.lower()))
existing = self._await_existing_torrent(torrent_info.info_hash, category)
except _QBITTORRENT_CLIENT_ERRORS as e:
logger.debug("Error checking for existing torrent: %s", e)
return None
else:
return None
return existing
+546
View File
@@ -0,0 +1,546 @@
"""Real-Debrid debrid service client for Shelfmark.
Routes magnet links through the Real-Debrid REST API (v1.0) to download
torrent content via Real-Debrid's CDN infrastructure.
"""
from __future__ import annotations
import shutil
import threading
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, ClassVar, NoReturn
import requests
from shelfmark.config.env import TMP_DIR
from shelfmark.core.config import config
from shelfmark.core.logger import setup_logger
from shelfmark.download.clients import (
DownloadClient,
DownloadState,
DownloadStatus,
register_client,
)
from shelfmark.download.clients._coercion import config_text
from shelfmark.download.clients.torrent_utils import (
DebridMagnet,
DebridUpload,
resolve_debrid_upload,
)
from shelfmark.download.http import download_url
from shelfmark.download.network import get_ssl_verify
logger = setup_logger(__name__)
_API_BASE = "https://api.real-debrid.com/rest/1.0"
_REALDEBRID_CLIENT_ERRORS = (
AttributeError,
OSError,
requests.exceptions.RequestException,
RuntimeError,
TypeError,
ValueError,
)
# Real-Debrid torrent status values.
_STATUS_DOWNLOADING = frozenset(
{
"magnet_conversion",
"waiting_files_selection",
"queued",
"downloading",
"compressing",
"uploading",
}
)
_STATUS_READY = "downloaded"
_STATUS_ERROR = frozenset({"error", "virus", "dead"})
# Timeouts for API calls.
_API_TIMEOUT = 30
_STATUS_TIMEOUT = 15
# File extensions recognised as book or audiobook content.
_BOOK_EXTENSIONS = (
".aac",
".azw",
".azw3",
".cbr",
".cbz",
".djvu",
".doc",
".docx",
".epub",
".fb2",
".flac",
".lit",
".m4a",
".m4b",
".mobi",
".mp3",
".mp4",
".ogg",
".opus",
".pdf",
".rtf",
".txt",
".wma",
)
def _raise_runtime_error(message: str) -> NoReturn:
raise RuntimeError(message)
@dataclass
class _DownloadState:
"""Internal mutable state for an in-progress Real-Debrid download."""
torrent_id: str
name: str
target_dir: Path
phase: str = "uploading"
error_message: str | None = None
progress: float = 0.0
download_thread: threading.Thread | None = None
lock: threading.Lock = field(default_factory=threading.Lock)
@register_client("torrent")
class RealDebridClient(DownloadClient):
"""Real-Debrid debrid service client.
Downloads torrent content by uploading magnet links to Real-Debrid,
selecting all files for download on their servers, then unrestricting
and fetching the resulting files via direct HTTP download from
Real-Debrid's CDN.
API documentation: https://api.real-debrid.com/
"""
protocol = "torrent"
name = "realdebrid"
_downloads: ClassVar[dict[str, _DownloadState]] = {}
_downloads_lock = threading.Lock()
def __init__(self) -> None:
self._api_key = config_text(config.get("REALDEBRID_API_KEY", ""))
def _auth_headers(self) -> dict[str, str]:
"""Return Authorization header dict for API requests."""
return {"Authorization": f"Bearer {self._api_key}"}
# ------------------------------------------------------------------
# DownloadClient interface
# ------------------------------------------------------------------
@staticmethod
def is_configured() -> bool:
"""Return True when Real-Debrid is selected and an API key exists."""
client = config_text(config.get("PROWLARR_TORRENT_CLIENT", ""))
api_key = config_text(config.get("REALDEBRID_API_KEY", ""))
return client == "realdebrid" and bool(api_key)
def test_connection(self) -> tuple[bool, str]:
"""Validate the API key and check Premium subscription status."""
if not self._api_key:
return False, "Real-Debrid API Key is required"
try:
url = f"{_API_BASE}/user"
resp = requests.get(
url,
headers=self._auth_headers(),
timeout=_STATUS_TIMEOUT,
verify=get_ssl_verify(url),
)
resp.raise_for_status()
user = resp.json()
username = user.get("username", "Unknown")
account_type = user.get("type", "free")
if account_type != "premium":
return (
False,
f"Real-Debrid user '{username}' does not have "
f"a Premium subscription (type: {account_type})",
)
except _REALDEBRID_CLIENT_ERRORS as e:
return False, f"Connection failed: {e}"
else:
return True, f"Connected to Real-Debrid as '{username}' (Premium)"
def add_download(
self,
url: str,
name: str,
category: str | None = None,
expected_hash: str | None = None,
**kwargs: object,
) -> str:
"""Send a torrent to Real-Debrid and select all files.
Accepts a magnet link, a .torrent URL, or an indexer proxy URL; anything
that is not already a magnet is resolved first, because Real-Debrid
answers a non-magnet body on addMagnet with a bare 404 (#1250).
"""
if not self._api_key:
msg = "Real-Debrid API key is not configured"
raise RuntimeError(msg)
try:
upload = resolve_debrid_upload(url, expected_hash=expected_hash)
data = self._send_torrent(upload)
torrent_id = str(data.get("id", ""))
if not torrent_id:
msg = "No torrent ID returned from Real-Debrid"
_raise_runtime_error(msg)
# Select all files so Real-Debrid starts downloading the torrent
select_url = f"{_API_BASE}/torrents/selectFiles/{torrent_id}"
sel_resp = requests.post(
select_url,
headers=self._auth_headers(),
data={"files": "all"},
timeout=_API_TIMEOUT,
verify=get_ssl_verify(select_url),
)
sel_resp.raise_for_status()
target_dir = TMP_DIR / f"realdebrid_{torrent_id}"
target_dir.mkdir(parents=True, exist_ok=True)
state = _DownloadState(
torrent_id=torrent_id,
name=name,
target_dir=target_dir,
phase="waiting_rd",
)
with self._downloads_lock:
self._downloads[torrent_id] = state
logger.info(
"Added torrent to Real-Debrid: ID %s (%s)",
torrent_id,
name,
)
except Exception:
logger.exception("Failed to add torrent to Real-Debrid")
raise
else:
return torrent_id
def _send_torrent(self, upload: DebridUpload) -> dict[str, Any]:
"""Hand the torrent to Real-Debrid, as a magnet or as a file upload."""
if isinstance(upload, DebridMagnet):
add_url = f"{_API_BASE}/torrents/addMagnet"
resp = requests.post(
add_url,
headers=self._auth_headers(),
data={"magnet": upload.magnet_url},
timeout=_API_TIMEOUT,
verify=get_ssl_verify(add_url),
)
else:
# addTorrent is a PUT that takes the raw file as the request body,
# not a form field: https://api.real-debrid.com/
add_url = f"{_API_BASE}/torrents/addTorrent"
resp = requests.put(
add_url,
headers={
**self._auth_headers(),
"Content-Type": "application/x-bittorrent",
},
data=upload.torrent_data,
timeout=_API_TIMEOUT,
verify=get_ssl_verify(add_url),
)
resp.raise_for_status()
return resp.json()
def get_status(self, download_id: str) -> DownloadStatus:
"""Poll Real-Debrid for torrent status and drive the download."""
state = self._ensure_state(download_id)
# Return cached terminal / in-flight states immediately.
with state.lock:
if state.phase == "error":
return DownloadStatus.error(
state.error_message or "Real-Debrid error",
)
if state.phase == "complete":
return DownloadStatus(
progress=100.0,
state=DownloadState.COMPLETE,
message="Complete",
complete=True,
file_path=str(state.target_dir),
)
if state.phase == "downloading_http":
return DownloadStatus(
progress=state.progress,
state=DownloadState.DOWNLOADING,
message="Downloading files via HTTP...",
complete=False,
file_path=None,
)
# Query Real-Debrid for torrent info.
try:
info_url = f"{_API_BASE}/torrents/info/{download_id}"
resp = requests.get(
info_url,
headers=self._auth_headers(),
timeout=_STATUS_TIMEOUT,
verify=get_ssl_verify(info_url),
)
resp.raise_for_status()
info = resp.json()
return self._handle_torrent_info(info, state)
except Exception as e:
logger.exception(
"Error checking Real-Debrid status for %s",
download_id,
)
return DownloadStatus.error(str(e))
def remove(
self,
download_id: str,
*,
delete_files: bool = False,
) -> bool:
"""Delete the torrent from Real-Debrid and clean up local files."""
try:
url = f"{_API_BASE}/torrents/delete/{download_id}"
requests.delete(
url,
headers=self._auth_headers(),
timeout=_STATUS_TIMEOUT,
verify=get_ssl_verify(url),
)
except _REALDEBRID_CLIENT_ERRORS as e:
logger.warning("Failed to delete torrent from Real-Debrid: %s", e)
with self._downloads_lock:
state = self._downloads.pop(download_id, None)
if state and state.target_dir.exists():
shutil.rmtree(state.target_dir, ignore_errors=True)
return True
def get_download_path(self, download_id: str) -> str | None:
"""Return the local directory containing downloaded files."""
with self._downloads_lock:
state = self._downloads.get(download_id)
if state and state.phase == "complete":
return str(state.target_dir)
target_dir = TMP_DIR / f"realdebrid_{download_id}"
if target_dir.exists():
return str(target_dir)
return None
# ------------------------------------------------------------------
# Internal helpers
# ------------------------------------------------------------------
def _ensure_state(self, download_id: str) -> _DownloadState:
"""Get or create download state for the given torrent ID."""
with self._downloads_lock:
state = self._downloads.get(download_id)
if state:
return state
target_dir = TMP_DIR / f"realdebrid_{download_id}"
state = _DownloadState(
torrent_id=download_id,
name=f"Download {download_id}",
target_dir=target_dir,
phase="waiting_rd",
)
with self._downloads_lock:
self._downloads[download_id] = state
return state
def _handle_torrent_info(
self,
info: dict[str, Any],
state: _DownloadState,
) -> DownloadStatus:
"""Map Real-Debrid torrent info to a DownloadStatus."""
status = info.get("status", "")
if status in _STATUS_DOWNLOADING:
progress = float(info.get("progress", 0.0))
speed = int(info.get("speed", 0))
filename = info.get("filename", state.name)
return DownloadStatus(
progress=progress * 0.5,
state=DownloadState.DOWNLOADING,
message=f"Real-Debrid downloading torrent ({filename})",
complete=False,
file_path=None,
download_speed=speed,
)
if status == _STATUS_READY:
links = info.get("links", [])
files = info.get("files", [])
self._maybe_start_download_thread(state, links, files)
return DownloadStatus(
progress=50.0,
state=DownloadState.DOWNLOADING,
message="Real-Debrid ready, retrieving files...",
complete=False,
file_path=None,
)
# Terminal error from Real-Debrid.
error_txt = f"Real-Debrid status error: {status}"
with state.lock:
state.phase = "error"
state.error_message = error_txt
return DownloadStatus.error(error_txt)
def _maybe_start_download_thread(
self,
state: _DownloadState,
links: list[str],
files: list[dict[str, Any]],
) -> None:
"""Spawn a background thread to unrestrict and download files."""
with state.lock:
already_running = state.phase in (
"unrestricting",
"downloading_http",
"complete",
)
thread_alive = state.download_thread is not None and state.download_thread.is_alive()
if already_running or thread_alive:
return
state.phase = "unrestricting"
t = threading.Thread(
target=self._process_and_download,
args=(state, links, files),
daemon=True,
)
state.download_thread = t
t.start()
# ------------------------------------------------------------------
# File download pipeline
# ------------------------------------------------------------------
def _process_and_download(
self,
state: _DownloadState,
links: list[str],
files: list[dict[str, Any]],
) -> None:
"""Unrestrict links and download files via HTTP.
Runs in a background thread spawned by ``_maybe_start_download_thread``.
"""
try:
if not links:
msg = "No download links returned by Real-Debrid"
_raise_runtime_error(msg)
# Match selected files with links
selected_files = [f for f in files if f.get("selected") == 1]
# Filter relevant ebook / audiobook files
relevant_indices: list[int] = []
for i, f_info in enumerate(selected_files):
path_str = f_info.get("path", "").lower()
if path_str.endswith(_BOOK_EXTENSIONS):
relevant_indices.append(i)
if not relevant_indices:
relevant_indices = list(range(len(links)))
with state.lock:
state.phase = "downloading_http"
total = len(relevant_indices)
for idx, rel_idx in enumerate(relevant_indices):
if rel_idx >= len(links):
continue
link = links[rel_idx]
# Unrestrict the Real-Debrid link to get direct CDN download URL
unrestrict_url = f"{_API_BASE}/unrestrict/link"
unl_resp = requests.post(
unrestrict_url,
headers=self._auth_headers(),
data={"link": link},
timeout=_API_TIMEOUT,
verify=get_ssl_verify(unrestrict_url),
)
unl_resp.raise_for_status()
unl_data = unl_resp.json()
direct_url = unl_data.get("download")
filename = unl_data.get("filename")
if not direct_url:
msg = f"Failed to unrestrict Real-Debrid link: {link}"
_raise_runtime_error(msg)
# Determine relative file path
if rel_idx < len(selected_files):
rel_path_str = selected_files[rel_idx].get("path", "").lstrip("/")
rel_path = Path(rel_path_str)
else:
rel_path = Path(filename or f"file_{idx + 1}")
dest = state.target_dir / rel_path
dest.parent.mkdir(parents=True, exist_ok=True)
logger.info(
"Downloading Real-Debrid file %d/%d: %s",
idx + 1,
total,
rel_path,
)
buf = download_url(
direct_url,
referer="https://real-debrid.com/",
)
if not buf:
msg = f"Failed to download from {direct_url}"
_raise_runtime_error(msg)
with dest.open("wb") as fh:
fh.write(buf.getvalue())
with state.lock:
state.progress = 50.0 + (idx + 1) / total * 50.0
with state.lock:
state.phase = "complete"
state.progress = 100.0
logger.info(
"Real-Debrid download complete for ID %s at %s",
state.torrent_id,
state.target_dir,
)
except Exception:
logger.exception(
"Error in Real-Debrid download for ID %s",
state.torrent_id,
)
with state.lock:
state.phase = "error"
state.error_message = str(
state.error_message or "Download failed",
)
+97 -7
View File
@@ -4,13 +4,19 @@ Uses xmlrpc to communicate with rTorrent's RPC interface.
"""
import ssl
import time
import xmlrpc.client as stdlib_xmlrpc_client
from typing import Any, NoReturn, Protocol, cast
from urllib.parse import urlparse
from shelfmark.core.config import config
from shelfmark.core.logger import setup_logger
from shelfmark.core.utils import get_hardened_xmlrpc_client
from shelfmark.core.utils import (
get_hardened_xmlrpc_client,
)
from shelfmark.core.utils import (
is_audiobook as check_audiobook,
)
from shelfmark.download.clients import (
DownloadClient,
DownloadStatus,
@@ -46,7 +52,13 @@ class _RTorrentLoadProtocol(Protocol):
def start(self, target: str, url: str, commands: str) -> object: ...
class _RTorrentCustom1Protocol(Protocol):
def set(self, download_id: str, value: str) -> object: ...
class _RTorrentDownloadProtocol(Protocol):
custom1: _RTorrentCustom1Protocol
def multicall2(self, *args: object) -> list[list[Any]]: ...
def delete_tied(self, download_id: str) -> object: ...
@@ -160,9 +172,14 @@ class RTorrentClient(DownloadClient):
try:
torrent_info = extract_torrent_info(url, expected_hash=expected_hash)
known_hashes: set[str] | None = None
if not (torrent_info.info_hash or expected_hash):
known_hashes = self._list_torrent_hashes()
commands = []
is_audiobook = kwargs.get("content_type") == "audiobook"
content_type = kwargs.get("content_type")
is_audiobook = check_audiobook(content_type if isinstance(content_type, str) else None)
default_label = (
self._audiobook_label if is_audiobook and self._audiobook_label else self._label
)
@@ -196,7 +213,15 @@ class RTorrentClient(DownloadClient):
torrent_hash = torrent_info.info_hash or expected_hash
if not torrent_hash:
_raise_runtime_error("Could not determine torrent hash from URL")
# rTorrent fetches .torrent URLs itself, so the add can succeed
# even when no hash could be extracted up front. Recover it by
# watching for the new download to appear.
torrent_hash = self._discover_added_torrent_hash(name, label, known_hashes)
if not torrent_hash:
message = "Could not determine torrent hash from URL"
if torrent_info.fetch_error:
message = f"{message} (torrent file fetch failed: {torrent_info.fetch_error})"
_raise_runtime_error(message)
logger.debug("Added torrent to rTorrent: %s", torrent_hash)
@@ -319,12 +344,14 @@ class RTorrentClient(DownloadClient):
"""
try:
# rtorrent is somehow case sensitive and requires uppercase hashes for look
torrent_hash = download_id.upper()
if delete_files:
self._rpc.d.delete_tied(download_id)
self._rpc.d.erase(download_id)
self._rpc.d.delete_tied(torrent_hash)
self._rpc.d.erase(torrent_hash)
else:
self._rpc.d.stop(download_id)
self._rpc.d.erase(download_id)
self._rpc.d.stop(torrent_hash)
self._rpc.d.erase(torrent_hash)
logger.info(
"Removed torrent from rTorrent: %s%s",
@@ -338,6 +365,19 @@ class RTorrentClient(DownloadClient):
else:
return True
def set_category(self, download_id: str, category: str) -> bool:
"""Assign a label to a torrent using rTorrent's custom1 field."""
try:
# rtorrent is somehow case sensitive and requires uppercase hashes for look
self._rpc.d.custom1.set(download_id.upper(), category)
logger.info("Set rTorrent label for %s to '%s'", download_id, category)
except _RTORRENT_CLIENT_ERRORS as e:
error_type = type(e).__name__
logger.exception("rTorrent set_category failed (%s)", error_type)
return False
else:
return True
def get_download_path(self, download_id: str) -> str | None:
"""Get the path where torrent files are located.
@@ -387,6 +427,56 @@ class RTorrentClient(DownloadClient):
except _RTORRENT_CLIENT_ERRORS:
return "/downloads"
def _list_torrent_hashes(self) -> set[str] | None:
"""Snapshot the hashes rTorrent currently reports."""
try:
all_torrents = self._rpc.d.multicall2("", "", "d.hash=")
except _RTORRENT_CLIENT_ERRORS as e:
logger.debug("Could not snapshot rTorrent downloads: %s", e)
return None
return {str(row[0]).lower() for row in all_torrents if row and row[0]}
def _discover_added_torrent_hash(
self,
name: str,
label: str,
known_hashes: set[str] | None,
) -> str | None:
"""Recover the hash of a torrent that was added without a known info_hash.
rTorrent fetches .torrent URLs itself, so the add can succeed even when
no hash could be extracted up front. A `known_hashes` of None means the
pre-add snapshot failed, so only an exact name match can identify the
new arrival.
"""
for _ in range(20):
try:
all_torrents = self._rpc.d.multicall2("", "", "d.hash=", "d.name=", "d.custom1=")
except _RTORRENT_CLIENT_ERRORS as e:
logger.debug("rTorrent hash discovery: %s", e)
else:
new_torrents = [
row
for row in all_torrents
if row
and row[0]
and (known_hashes is None or str(row[0]).lower() not in known_hashes)
]
# The label set at add time distinguishes concurrent arrivals,
# but rTorrent may not have applied it yet, so it only ever
# narrows a non-empty candidate list.
if label:
labeled = [row for row in new_torrents if len(row) > 2 and row[2] == label]
if labeled:
new_torrents = labeled
for row in new_torrents:
if len(row) > 1 and row[1] == name:
return str(row[0]).lower()
if known_hashes is not None and len(new_torrents) == 1:
return str(new_torrents[0][0]).lower()
time.sleep(0.5)
return None
def _get_torrent_path(self, download_id: str) -> str | None:
"""Get the file path of a torrent by hash.
+9
View File
@@ -248,6 +248,15 @@ class SABnzbdClient(DownloadClient):
if trusted_url and _url_origin(trusted_url) == target_origin:
return True
named_indexers = config.get("NEWZNAB_INDEXERS", [])
if isinstance(named_indexers, list):
for row in named_indexers:
if not isinstance(row, dict):
continue
trusted_url = normalize_http_config_url(row.get("url"))
if trusted_url and _url_origin(trusted_url) == target_origin:
return True
return False
def _get_prowlarr_headers(self, url: str) -> dict:
+135 -2
View File
@@ -159,6 +159,7 @@ def _test_qbittorrent_connection(current_values: dict[str, Any] | None = None) -
raw_url = _resolve_string_setting(current_values, config.get, "QBITTORRENT_URL")
username = _resolve_string_setting(current_values, config.get, "QBITTORRENT_USERNAME")
password = _resolve_string_setting(current_values, config.get, "QBITTORRENT_PASSWORD")
api_key = _resolve_string_setting(current_values, config.get, "QBITTORRENT_API_KEY")
if not raw_url:
return {"success": False, "message": "qBittorrent URL is required"}
@@ -174,6 +175,7 @@ def _test_qbittorrent_connection(current_values: dict[str, Any] | None = None) -
host=url,
username=username,
password=password,
api_key=api_key or None,
VERIFY_WEBUI_CERTIFICATE=get_ssl_verify(url),
)
client.auth_log_in()
@@ -181,9 +183,18 @@ def _test_qbittorrent_connection(current_values: dict[str, Any] | None = None) -
except ImportError:
return {"success": False, "message": "qbittorrent-api package not installed"}
except _QBITTORRENT_SETTINGS_ERRORS as e:
if isinstance(e, _QBittorrentLoginFailed):
# LoginFailed carries no message of its own, so name the rejected credential.
rejected = "API key" if api_key else "username or password"
return {"success": False, "message": f"qBittorrent rejected the {rejected}"}
return {"success": False, "message": f"Connection failed: {e!s}"}
else:
return {"success": True, "message": f"Connected to qBittorrent (API v{api_version})"}
# Both credentials can be set at once, so name the one that actually authenticated.
used = " using the API key" if api_key else ""
return {
"success": True,
"message": f"Connected to qBittorrent (API v{api_version}){used}",
}
def _test_transmission_connection(current_values: dict[str, Any] | None = None) -> dict[str, Any]:
@@ -520,6 +531,57 @@ def _test_sabnzbd_connection(current_values: dict[str, Any] | None = None) -> di
return {"success": True, "message": f"Connected to SABnzbd {version}"}
def _test_alldebrid_connection(current_values: dict[str, Any] | None = None) -> dict[str, Any]:
"""Test the AllDebrid API connection using current form values."""
from shelfmark.core.config import config
from shelfmark.download.clients.alldebrid import AllDebridClient
current_values = current_values or {}
api_key = _resolve_string_setting(current_values, config.get, "ALLDEBRID_API_KEY")
if not api_key:
return {"success": False, "message": "AllDebrid API Key is required"}
client = AllDebridClient()
client._api_key = api_key
success, message = client.test_connection()
return {"success": success, "message": message}
def _test_realdebrid_connection(current_values: dict[str, Any] | None = None) -> dict[str, Any]:
"""Test the Real-Debrid API connection using current form values."""
from shelfmark.core.config import config
from shelfmark.download.clients.realdebrid import RealDebridClient
current_values = current_values or {}
api_key = _resolve_string_setting(current_values, config.get, "REALDEBRID_API_KEY")
if not api_key:
return {"success": False, "message": "Real-Debrid API Key is required"}
client = RealDebridClient()
client._api_key = api_key
success, message = client.test_connection()
return {"success": success, "message": message}
def _test_torbox_connection(current_values: dict[str, Any] | None = None) -> dict[str, Any]:
"""Test the TorBox API connection using current form values."""
from shelfmark.core.config import config
from shelfmark.download.clients.torbox import TorBoxClient
current_values = current_values or {}
api_key = _resolve_string_setting(current_values, config.get, "TORBOX_API_KEY")
if not api_key:
return {"success": False, "message": "TorBox API Key is required"}
client = TorBoxClient()
client._api_key = api_key
success, message = client.test_connection()
return {"success": success, "message": message}
# ==================== Download Clients Tab ====================
@@ -544,13 +606,69 @@ def prowlarr_clients_settings() -> list[SettingsField]:
description="Choose which torrent client to use",
options=[
{"value": "", "label": "None"},
{"value": "alldebrid", "label": "AllDebrid"},
{"value": "blackhole", "label": "Blackhole"},
{"value": "qbittorrent", "label": "qBittorrent"},
{"value": "realdebrid", "label": "Real-Debrid"},
{"value": "torbox", "label": "TorBox"},
{"value": "transmission", "label": "Transmission"},
{"value": "deluge", "label": "Deluge"},
{"value": "rtorrent", "label": "rTorrent"},
],
default="",
),
TextField(
key="BLACKHOLE_DIRECTORY",
label="Blackhole Directory",
description="Directory where Shelfmark saves .torrent files for another downloader",
placeholder="/blackhole",
show_when={"field": "PROWLARR_TORRENT_CLIENT", "value": "blackhole"},
),
# --- AllDebrid Settings ---
PasswordField(
key="ALLDEBRID_API_KEY",
label="API Key",
description="AllDebrid API Key (apiv4) from your AllDebrid account settings",
show_when={"field": "PROWLARR_TORRENT_CLIENT", "value": "alldebrid"},
),
ActionButton(
key="test_alldebrid",
label="Test Connection",
description="Verify your AllDebrid configuration",
style="primary",
callback=_test_alldebrid_connection,
show_when={"field": "PROWLARR_TORRENT_CLIENT", "value": "alldebrid"},
),
# --- Real-Debrid Settings ---
PasswordField(
key="REALDEBRID_API_KEY",
label="API Key",
description="Real-Debrid API Key (Secret Token) from your Real-Debrid account settings",
show_when={"field": "PROWLARR_TORRENT_CLIENT", "value": "realdebrid"},
),
ActionButton(
key="test_realdebrid",
label="Test Connection",
description="Verify your Real-Debrid configuration",
style="primary",
callback=_test_realdebrid_connection,
show_when={"field": "PROWLARR_TORRENT_CLIENT", "value": "realdebrid"},
),
# --- TorBox Settings ---
PasswordField(
key="TORBOX_API_KEY",
label="API Key",
description="TorBox API Key from your TorBox account settings",
show_when={"field": "PROWLARR_TORRENT_CLIENT", "value": "torbox"},
),
ActionButton(
key="test_torbox",
label="Test Connection",
description="Verify your TorBox configuration",
style="primary",
callback=_test_torbox_connection,
show_when={"field": "PROWLARR_TORRENT_CLIENT", "value": "torbox"},
),
# --- qBittorrent Settings ---
TextField(
key="QBITTORRENT_URL",
@@ -572,6 +690,12 @@ def prowlarr_clients_settings() -> list[SettingsField]:
description="qBittorrent Web UI password",
show_when={"field": "PROWLARR_TORRENT_CLIENT", "value": "qbittorrent"},
),
PasswordField(
key="QBITTORRENT_API_KEY",
label="API Key",
description="Found in qBittorrent: Options > Web UI > API Key (qBittorrent 5.2.0+). Used instead of the username and password when set.",
show_when={"field": "PROWLARR_TORRENT_CLIENT", "value": "qbittorrent"},
),
ActionButton(
key="test_qbittorrent",
label="Test Connection",
@@ -771,14 +895,23 @@ def prowlarr_clients_settings() -> list[SettingsField]:
SelectField(
key="PROWLARR_TORRENT_ACTION",
label="Torrent Completion Action",
description="Remove deletes the torrent from your client immediately after import (stops seeding, files are kept); Keep leaves it in the client to continue seeding",
description="Choose whether to keep, remove, or move the torrent to another category or label after import",
options=[
{"value": "keep", "label": "Keep"},
{"value": "remove", "label": "Remove"},
{"value": "change_category", "label": "Change Category"},
],
default="keep",
show_when={"field": "PROWLARR_TORRENT_CLIENT", "notEmpty": True},
),
TextField(
key="PROWLARR_TORRENT_POST_IMPORT_CATEGORY",
label="Post-Import Category",
description="Category or label to assign after a successful import",
placeholder="imported",
default="",
show_when={"field": "PROWLARR_TORRENT_ACTION", "value": "change_category"},
),
# --- Usenet Client Selection ---
HeadingField(
key="usenet_heading",
+618
View File
@@ -0,0 +1,618 @@
"""TorBox debrid service client for Shelfmark."""
from __future__ import annotations
import math
import shutil
import threading
from dataclasses import dataclass, field
from pathlib import Path, PurePosixPath, PureWindowsPath
from typing import Any, ClassVar, NoReturn
from urllib.parse import urlparse
import requests
from shelfmark.config.env import TMP_DIR
from shelfmark.core.config import config
from shelfmark.core.logger import setup_logger
from shelfmark.download.clients import (
DownloadClient,
DownloadState,
DownloadStatus,
register_client,
)
from shelfmark.download.clients._coercion import config_text
from shelfmark.download.clients.torrent_utils import (
DebridMagnet,
DebridUpload,
resolve_debrid_upload,
)
from shelfmark.download.http import download_url
from shelfmark.download.network import get_ssl_verify
logger = setup_logger(__name__)
_API_BASE = "https://api.torbox.app/v1/api"
_API_TIMEOUT = 30
_STATUS_TIMEOUT = 15
_WORKER_JOIN_TIMEOUT = 5.0
_BOOK_EXTENSIONS = (
".aac",
".azw",
".azw3",
".cbr",
".cbz",
".djvu",
".doc",
".docx",
".epub",
".fb2",
".flac",
".lit",
".m4a",
".m4b",
".mobi",
".mp3",
".mp4",
".ogg",
".opus",
".pdf",
".rtf",
".txt",
".wma",
)
_TERMINAL_STATES = frozenset({"error", "failed", "missingfiles", "dead"})
_PLAN_NAMES = {0: "Free", 1: "Essential", 2: "Pro", 3: "Standard"}
def _raise_runtime_error(message: str) -> NoReturn:
raise RuntimeError(message)
@dataclass
class _DownloadState:
"""Internal mutable state for an in-progress TorBox download."""
torrent_id: str
name: str
target_dir: Path
phase: str = "waiting_torbox"
error_message: str | None = None
progress: float = 0.0
download_thread: threading.Thread | None = None
cancel_event: threading.Event = field(default_factory=threading.Event)
lock: threading.Lock = field(default_factory=threading.Lock)
@register_client("torrent")
class TorBoxClient(DownloadClient):
"""Download torrent content through TorBox and its CDN."""
protocol = "torrent"
name = "torbox"
prefers_torrent_file = True
_downloads: ClassVar[dict[str, _DownloadState]] = {}
_downloads_lock = threading.Lock()
def __init__(self) -> None:
self._api_key = config_text(config.get("TORBOX_API_KEY", ""))
def _auth_headers(self) -> dict[str, str]:
"""Return the authorization headers used by TorBox API calls."""
return {"Authorization": f"Bearer {self._api_key}"}
@staticmethod
def is_configured() -> bool:
"""Return True when TorBox is selected and an API key exists."""
client = config_text(config.get("PROWLARR_TORRENT_CLIENT", ""))
api_key = config_text(config.get("TORBOX_API_KEY", ""))
return client == "torbox" and bool(api_key)
def test_connection(self) -> tuple[bool, str]:
"""Validate the API key and report the connected TorBox plan."""
if not self._api_key:
return False, "TorBox API Key is required"
try:
user = self._request_data(
"GET",
"/user/me",
operation="account lookup",
params={"settings": "false"},
timeout=_STATUS_TIMEOUT,
)
if not isinstance(user, dict):
_raise_runtime_error("TorBox account lookup returned invalid user data")
except (
OSError,
requests.exceptions.RequestException,
RuntimeError,
TypeError,
ValueError,
) as e:
return False, f"Connection failed: {e}"
plan_value = user.get("plan")
plan = _PLAN_NAMES.get(plan_value, "Unknown") if isinstance(plan_value, int) else "Unknown"
email = user.get("email")
account = f" as '{email}'" if isinstance(email, str) and email else ""
return True, f"Connected to TorBox{account} ({plan} plan)"
def add_download(
self,
url: str,
name: str,
category: str | None = None,
expected_hash: str | None = None,
**kwargs: Any,
) -> str:
"""Send a magnet or torrent file to TorBox and return its torrent ID."""
if not self._api_key:
_raise_runtime_error("TorBox API key is not configured")
try:
upload = resolve_debrid_upload(url, expected_hash=expected_hash)
data = self._send_torrent(upload, name)
torrent_id = self._normalize_torrent_id(data.get("torrent_id"))
target_dir = TMP_DIR / f"torbox_{torrent_id}"
target_dir.mkdir(parents=True, exist_ok=True)
state = _DownloadState(torrent_id=torrent_id, name=name, target_dir=target_dir)
with self._downloads_lock:
self._downloads[torrent_id] = state
logger.info(
"Added torrent to TorBox: ID %s", torrent_id, extra={"torrent_id": torrent_id}
)
except Exception:
logger.exception("Failed to add torrent to TorBox")
raise
else:
return torrent_id
def _send_torrent(self, upload: DebridUpload, name: str) -> dict[str, Any]:
"""Create a TorBox torrent from a magnet link or torrent file."""
endpoint = "/torrents/createtorrent"
data: dict[str, str] = {"name": name}
files: dict[str, tuple[str, bytes, str]] | None = None
if isinstance(upload, DebridMagnet):
data["magnet"] = upload.magnet_url
else:
files = {
"file": (
"release.torrent",
upload.torrent_data,
"application/x-bittorrent",
)
}
result = self._request_data(
"POST",
endpoint,
operation="torrent creation",
data=data,
files=files,
timeout=_API_TIMEOUT,
)
if not isinstance(result, dict):
_raise_runtime_error("TorBox torrent creation returned invalid data")
return result
def get_status(self, download_id: str) -> DownloadStatus:
"""Poll TorBox for torrent status and drive local file retrieval."""
download_id = self._normalize_torrent_id(download_id)
state = self._ensure_state(download_id)
with state.lock:
if state.phase == "error":
return DownloadStatus.error(state.error_message or "TorBox download failed")
if state.phase == "complete":
return DownloadStatus(
progress=100.0,
state=DownloadState.COMPLETE,
message="Complete",
complete=True,
file_path=str(state.target_dir),
)
if state.phase == "downloading_http":
return DownloadStatus(
progress=state.progress,
state=DownloadState.DOWNLOADING,
message="Downloading files via TorBox...",
complete=False,
file_path=None,
)
try:
data = self._request_data(
"GET",
"/torrents/mylist",
operation="torrent status lookup",
params={"id": download_id, "bypass_cache": "true"},
timeout=_STATUS_TIMEOUT,
)
torrent = self._extract_torrent(data, download_id)
return self._handle_torrent_status(torrent, state)
except Exception as e:
logger.exception(
"Failed to check TorBox torrent status",
extra={"torrent_id": download_id},
)
return DownloadStatus.error(f"TorBox status check failed: {e}")
def remove(self, download_id: str, *, delete_files: bool = False) -> bool:
"""Delete the remote torrent and clean up its local temporary directory."""
download_id = self._normalize_torrent_id(download_id)
remote_removed = True
try:
self._request_data(
"POST",
"/torrents/controltorrent",
operation="torrent deletion",
json={"torrent_id": int(download_id), "operation": "delete"},
timeout=_STATUS_TIMEOUT,
require_data=False,
)
except OSError, requests.exceptions.RequestException, RuntimeError, TypeError, ValueError:
remote_removed = False
logger.warning("Failed to delete TorBox torrent", extra={"torrent_id": download_id})
with self._downloads_lock:
state = self._downloads.get(download_id)
if state:
with state.lock:
state.cancel_event.set()
if state.download_thread and state.download_thread is not threading.current_thread():
state.download_thread.join(_WORKER_JOIN_TIMEOUT)
if state.download_thread.is_alive():
logger.warning(
"TorBox retrieval thread did not stop; deferring cleanup",
extra={"torrent_id": download_id},
)
return False
with self._downloads_lock:
state = self._downloads.pop(download_id, None)
target_dir = state.target_dir if state else TMP_DIR / f"torbox_{download_id}"
local_removed = True
if target_dir.exists():
try:
shutil.rmtree(target_dir)
except OSError:
local_removed = False
logger.warning(
"Failed to remove TorBox temporary files",
extra={"torrent_id": download_id},
)
return remote_removed and local_removed
def get_download_path(self, download_id: str) -> str | None:
"""Return the local directory once TorBox files have been retrieved."""
download_id = self._normalize_torrent_id(download_id)
with self._downloads_lock:
state = self._downloads.get(download_id)
if state and state.phase == "complete":
return str(state.target_dir)
return None
def _request_data(
self,
method: str,
endpoint: str,
*,
operation: str,
require_data: bool = True,
**kwargs: object,
) -> Any:
"""Send a TorBox request and validate its JSON response envelope."""
url = f"{_API_BASE}{endpoint}"
request_kwargs: Any = {
"headers": self._auth_headers(),
"verify": get_ssl_verify(url),
**kwargs,
}
request: Any = requests.get if method == "GET" else requests.post
try:
response = request(url, **request_kwargs)
except requests.exceptions.RequestException as e:
raise RuntimeError(f"TorBox {operation} failed: {type(e).__name__}") from None
try:
payload = response.json()
except (AttributeError, TypeError, ValueError) as e:
_raise_runtime_error(f"TorBox {operation} returned invalid JSON: {e}")
if not isinstance(payload, dict):
_raise_runtime_error(f"TorBox {operation} returned an invalid response")
error = payload.get("error")
detail = payload.get("detail")
status_code = getattr(response, "status_code", 200)
if not isinstance(status_code, int) or not 200 <= status_code < 300:
message = detail if isinstance(detail, str) and detail else f"HTTP {status_code}"
code = f" [{error}]" if isinstance(error, str) and error else ""
_raise_runtime_error(f"TorBox {operation} failed{code}: {message}")
if payload.get("success") is not True or error:
message = detail if isinstance(detail, str) and detail else "Unknown TorBox error"
code = f" [{error}]" if isinstance(error, str) and error else ""
_raise_runtime_error(f"TorBox {operation} failed{code}: {message}")
data = payload.get("data")
if require_data and data is None:
_raise_runtime_error(f"TorBox {operation} returned no data")
return data
def _ensure_state(self, download_id: str) -> _DownloadState:
"""Get or create download state for a TorBox torrent ID."""
download_id = self._normalize_torrent_id(download_id)
with self._downloads_lock:
state = self._downloads.get(download_id)
if state is None:
state = _DownloadState(
torrent_id=download_id,
name=f"Download {download_id}",
target_dir=TMP_DIR / f"torbox_{download_id}",
)
self._downloads[download_id] = state
return state
@staticmethod
def _normalize_torrent_id(value: object) -> str:
"""Return a canonical positive decimal TorBox torrent ID."""
torrent_id = str(value) if value is not None else ""
if not torrent_id.isascii() or not torrent_id.isdecimal():
_raise_runtime_error("TorBox returned an invalid torrent ID")
normalized = str(int(torrent_id))
if normalized == "0":
_raise_runtime_error("TorBox returned an invalid torrent ID")
return normalized
@staticmethod
def _extract_torrent(data: Any, download_id: str) -> dict[str, Any]:
"""Extract the requested torrent from TorBox's object or list response."""
if isinstance(data, dict):
return data
if isinstance(data, list):
for torrent in data:
if isinstance(torrent, dict) and str(torrent.get("id", "")) == download_id:
return torrent
_raise_runtime_error(f"TorBox torrent {download_id} was not found")
def _handle_torrent_status(
self,
torrent: dict[str, Any],
state: _DownloadState,
) -> DownloadStatus:
"""Map a TorBox torrent object into Shelfmark download status."""
remote_state = str(torrent.get("download_state", "unknown"))
normalized_state = remote_state.lower()
if normalized_state in _TERMINAL_STATES:
message = torrent.get("tracker_message") or f"TorBox status error: {remote_state}"
self._set_error(state, str(message))
return DownloadStatus.error(str(message))
finished = torrent.get("download_finished") is True
present = torrent.get("download_present") is True
if finished and not present:
message = "TorBox finished processing but the download is unavailable"
self._set_error(state, message)
return DownloadStatus.error(message)
if finished and present:
files = torrent.get("files")
if not isinstance(files, list):
message = "TorBox returned no file list for a completed torrent"
self._set_error(state, message)
return DownloadStatus.error(message)
self._maybe_start_download_thread(state, files)
return DownloadStatus(
progress=50.0,
state=DownloadState.DOWNLOADING,
message="TorBox ready, retrieving files...",
complete=False,
file_path=None,
)
progress = self._normalize_remote_progress(torrent.get("progress")) * 0.5
speed = self._integer_value(torrent.get("download_speed"))
eta = self._integer_value(torrent.get("eta"))
name = torrent.get("name") or state.name
return DownloadStatus(
progress=progress,
state=DownloadState.DOWNLOADING,
message=f"TorBox processing torrent ({name}: {remote_state})",
complete=False,
file_path=None,
download_speed=speed,
eta=eta,
)
@staticmethod
def _normalize_remote_progress(value: object) -> float:
"""Normalize fractional or percentage TorBox progress to 0 through 100."""
if not isinstance(value, int | float | str):
return 0.0
try:
progress = float(value)
except TypeError, ValueError:
return 0.0
if not math.isfinite(progress):
return 0.0
if 0.0 <= progress <= 1.0:
progress *= 100.0
return max(0.0, min(100.0, progress))
@staticmethod
def _integer_value(value: object) -> int | None:
"""Return an integer metric when TorBox provided a numeric value."""
if not isinstance(value, int | float | str):
return None
try:
return int(value)
except TypeError, ValueError:
return None
def _maybe_start_download_thread(
self,
state: _DownloadState,
files: list[dict[str, Any]],
) -> None:
"""Start exactly one background worker to retrieve TorBox files."""
with state.lock:
already_running = state.phase in {"downloading_http", "complete"}
thread_alive = state.download_thread is not None and state.download_thread.is_alive()
if already_running or thread_alive:
return
state.phase = "downloading_http"
state.progress = 50.0
state.download_thread = threading.Thread(
target=self._process_and_download,
args=(state, files),
daemon=True,
)
state.download_thread.start()
def _process_and_download(self, state: _DownloadState, files: list[dict[str, Any]]) -> None:
"""Request direct file links from TorBox and download supported content."""
try:
if state.cancel_event.is_set():
return
relevant = [
file_info
for file_info in files
if self._file_name(file_info).lower().endswith(_BOOK_EXTENSIONS)
]
if not relevant:
_raise_runtime_error("TorBox torrent contains no supported book or audiobook files")
with state.lock:
if state.cancel_event.is_set():
return
state.target_dir.mkdir(parents=True, exist_ok=True)
for index, file_info in enumerate(relevant, start=1):
if state.cancel_event.is_set():
return
file_id = self._file_id(file_info)
relative_path = self._safe_relative_path(file_info, state.target_dir)
direct_url = self._request_download_link(state.torrent_id, file_id)
buffer = download_url(
direct_url,
referer="https://torbox.app/",
cancel_flag=state.cancel_event,
)
if state.cancel_event.is_set():
return
if not buffer:
_raise_runtime_error(
f"TorBox file download failed for torrent {state.torrent_id}, file {file_id}"
)
destination = state.target_dir / relative_path
destination.parent.mkdir(parents=True, exist_ok=True)
with destination.open("wb") as output:
buffer.seek(0)
shutil.copyfileobj(buffer, output)
with state.lock:
state.progress = 50.0 + index / len(relevant) * 50.0
with state.lock:
if state.cancel_event.is_set():
return
state.phase = "complete"
state.progress = 100.0
logger.info(
"TorBox download complete: ID %s",
state.torrent_id,
extra={"torrent_id": state.torrent_id},
)
except Exception as e:
if state.cancel_event.is_set():
logger.info(
"TorBox file retrieval cancelled",
extra={"torrent_id": state.torrent_id},
)
return
logger.exception(
"TorBox file retrieval failed",
extra={"torrent_id": state.torrent_id},
)
self._set_error(state, str(e) or "TorBox file retrieval failed")
def _request_download_link(self, torrent_id: str, file_id: int) -> str:
"""Request a temporary direct link without exposing the token in messages."""
data = self._request_data(
"GET",
"/torrents/requestdl",
operation="file-link request",
params={
"token": self._api_key,
"torrent_id": torrent_id,
"file_id": file_id,
"redirect": "false",
"append_name": "true",
},
timeout=_API_TIMEOUT,
)
if not isinstance(data, str):
_raise_runtime_error(
f"TorBox returned an invalid download link for torrent {torrent_id}, file {file_id}"
)
parsed = urlparse(data)
if parsed.scheme != "https" or not parsed.hostname:
_raise_runtime_error(
f"TorBox returned an invalid download link for torrent {torrent_id}, file {file_id}"
)
return data
@staticmethod
def _file_name(file_info: dict[str, Any]) -> str:
"""Return the provider path, falling back to its shortened name."""
name = file_info.get("name")
if isinstance(name, str) and name.strip():
return name
short_name = file_info.get("short_name")
return short_name.strip() if isinstance(short_name, str) else ""
@staticmethod
def _file_id(file_info: dict[str, Any]) -> int:
"""Return a validated TorBox file ID."""
try:
return int(file_info["id"])
except (KeyError, TypeError, ValueError) as e:
_raise_runtime_error(f"TorBox returned an invalid file ID: {e}")
@classmethod
def _safe_relative_path(cls, file_info: dict[str, Any], target_dir: Path) -> Path:
"""Validate external file metadata before writing below ``target_dir``."""
name = cls._file_name(file_info)
if not name:
_raise_runtime_error("TorBox returned a file without a name")
normalized = name.replace("\\", "/")
relative_path = PurePosixPath(normalized)
windows_path = PureWindowsPath(name)
if (
relative_path.is_absolute()
or windows_path.is_absolute()
or windows_path.drive
or ".." in relative_path.parts
):
_raise_runtime_error(f"TorBox returned an unsafe file path: {name}")
if relative_path == PurePosixPath("."):
_raise_runtime_error("TorBox returned a file without a usable name")
destination = (target_dir / Path(*relative_path.parts)).resolve()
try:
destination.relative_to(target_dir.resolve())
except ValueError:
_raise_runtime_error(f"TorBox returned an unsafe file path: {name}")
return Path(*relative_path.parts)
@staticmethod
def _set_error(state: _DownloadState, message: str) -> None:
"""Record a terminal local error for later polling calls."""
with state.lock:
state.phase = "error"
state.error_message = message
+237 -50
View File
@@ -5,9 +5,11 @@ from __future__ import annotations
import base64
import hashlib
import re
import time
from binascii import Error as BinasciiError
from dataclasses import dataclass
from urllib.parse import ParseResult, parse_qs, urljoin, urlparse
from threading import Lock
from urllib.parse import ParseResult, parse_qs, urljoin, urlparse, urlunparse
import requests
@@ -15,10 +17,12 @@ from shelfmark.core.config import config
from shelfmark.core.logger import setup_logger
from shelfmark.core.utils import normalize_http_url
from shelfmark.download.network import get_ssl_verify
from shelfmark.download.postprocess.packs import PackFile
logger = setup_logger(__name__)
_MAGNET_RESPONSE_MAX_BYTES = 2000
_TORRENT_FETCH_MAX_REDIRECTS = 5
_BASE32_BTMH_TAG_BYTES = 34
_BTIH_INFO_BYTE_HEX = 0x20
_BTIH_PREFIX_BYTE = 0x12
@@ -35,9 +39,40 @@ _TORRENT_FETCH_ERRORS = (
_TORRENT_PARSE_ERRORS = (IndexError, KeyError, TypeError, ValueError)
_TRUSTED_TORRENT_FETCH_URL_CONFIG_KEYS = ("PROWLARR_URL", "NEWZNAB_URL")
# Successful torrent fetches are reused for a short window so one add attempt
# hits the download link only once. Tracker download links (e.g. private
# trackers behind Prowlarr's proxy) can be slow, rate-limited, or single-use,
# and both find_existing() and add_download() resolve the same URL (#1111).
_TORRENT_FETCH_CACHE_TTL_SECONDS = 120.0
_TORRENT_FETCH_CACHE_MAX_ENTRIES = 8
_torrent_fetch_cache_lock = Lock()
_torrent_fetch_cache: dict[str, tuple[float, TorrentInfo]] = {}
type BencodeValue = dict[str | bytes, BencodeValue] | list[BencodeValue] | int | bytes | str
def _safe_url(url: str, *, limit: int = 120) -> str:
"""Return a log-safe URL: scheme/host/path kept, query and fragment dropped.
Torrent download URLs commonly carry credentials in their query string
(Prowlarr's ``apikey=...`` proxy links among them), so raw URLs must never
reach logs or exception messages.
"""
parsed = urlparse(url)
if not parsed.scheme or not parsed.netloc:
return f"<unparsed url: {type(url).__name__}>"
safe = urlunparse(parsed._replace(query="", fragment=""))
return safe[:limit]
_URL_IN_TEXT_PATTERN = re.compile(r"https?://\S+")
def _redact_urls_in_text(text: str) -> str:
"""Scrub credential-bearing URLs out of free-form text such as exception messages."""
return _URL_IN_TEXT_PATTERN.sub(lambda match: _safe_url(match.group(0)), text)
@dataclass
class TorrentInfo:
"""Parsed information from a torrent URL."""
@@ -54,6 +89,9 @@ class TorrentInfo:
magnet_url: str | None = None
"""The actual magnet URL, if available."""
fetch_error: str | None = None
"""Why fetching the .torrent URL failed, or None if it succeeded/was skipped."""
def with_info_hash(self, info_hash: str | None) -> TorrentInfo:
"""Return a copy with the info_hash replaced when provided."""
if info_hash:
@@ -62,10 +100,65 @@ class TorrentInfo:
torrent_data=self.torrent_data,
is_magnet=self.is_magnet,
magnet_url=self.magnet_url,
fetch_error=self.fetch_error,
)
return self
@dataclass
class DebridMagnet:
"""A magnet link, ready to hand to a debrid service as-is."""
magnet_url: str
@dataclass
class DebridTorrentFile:
"""Raw .torrent bytes, for a debrid service's file-upload endpoint."""
torrent_data: bytes
# A debrid service takes one or the other, never an indexer page or a proxy URL.
type DebridUpload = DebridMagnet | DebridTorrentFile
def resolve_debrid_upload(url: str, *, expected_hash: str | None = None) -> DebridUpload:
"""Resolve a release download URL into a magnet link or .torrent bytes.
Prowlarr hands out a proxy URL, with no magnetUrl and no infoHash, for any
indexer that only publishes torrent files - 1337x among them. Posting that
URL to a debrid service as if it were a magnet is what produced a bare 404
from the service instead of a download (#1250).
The torrent file is preferred over a synthesized `urn:btih:` magnet because
it carries the tracker list, which is how the service finds a swarm that is
not already cached. Fetches are shared with the rest of the add path through
the torrent fetch cache, so resolving here costs at most one request.
Raises:
ValueError: The URL resolved to neither form, so there is nothing to send.
"""
if url.startswith("magnet:"):
return DebridMagnet(magnet_url=url)
info = extract_torrent_info(url, expected_hash=expected_hash)
if info.is_magnet and info.magnet_url:
# The download URL redirected to, or returned, a magnet link.
return DebridMagnet(magnet_url=info.magnet_url)
if info.torrent_data:
return DebridTorrentFile(torrent_data=info.torrent_data)
if info.info_hash:
# No file to upload, but the hash alone still identifies the torrent.
return DebridMagnet(magnet_url=f"magnet:?xt=urn:btih:{info.info_hash}")
reason = info.fetch_error or "no magnet link, info hash, or torrent file was available"
msg = f"Could not resolve a torrent to send from {_safe_url(url)} ({reason})"
raise ValueError(msg)
def extract_torrent_info(
url: str,
*,
@@ -96,21 +189,62 @@ def extract_torrent_info(
if not fetch_torrent:
return TorrentInfo(info_hash=expected_hash, torrent_data=None, is_magnet=False)
info = _get_cached_torrent_fetch(url)
if info is None:
info = _fetch_torrent_info(url)
if info.fetch_error is None:
_store_cached_torrent_fetch(url, info)
return info.with_info_hash(info.info_hash or expected_hash)
def _get_cached_torrent_fetch(url: str) -> TorrentInfo | None:
with _torrent_fetch_cache_lock:
entry = _torrent_fetch_cache.get(url)
if entry is None:
return None
fetched_at, info = entry
if time.monotonic() - fetched_at > _TORRENT_FETCH_CACHE_TTL_SECONDS:
del _torrent_fetch_cache[url]
return None
logger.debug("Reusing recently fetched torrent data for: %s...", _safe_url(url))
return info
def _store_cached_torrent_fetch(url: str, info: TorrentInfo) -> None:
with _torrent_fetch_cache_lock:
_torrent_fetch_cache[url] = (time.monotonic(), info)
while len(_torrent_fetch_cache) > _TORRENT_FETCH_CACHE_MAX_ENTRIES:
oldest_url = min(_torrent_fetch_cache, key=lambda key: _torrent_fetch_cache[key][0])
del _torrent_fetch_cache[oldest_url]
def clear_torrent_fetch_cache() -> None:
"""Drop all cached torrent fetches (used by tests)."""
with _torrent_fetch_cache_lock:
_torrent_fetch_cache.clear()
def _fetch_torrent_info(url: str) -> TorrentInfo:
"""Fetch a .torrent URL and parse out the info_hash and raw torrent data.
On failure, the returned TorrentInfo carries the reason in `fetch_error`
so callers can surface it instead of a generic hash error.
"""
# A release source can legitimately hand us a download URL on a different
# origin than the configured Prowlarr/Newznab endpoint (e.g. a direct
# tracker link, or Prowlarr reached through a separate proxy). We still need
# to fetch the .torrent to recover the info_hash when the source did not
# provide one, so the prefetch runs regardless of origin. The Prowlarr API
# key, however, is only ever sent to a trusted origin so it can never leak
# to an arbitrary indexer/tracker host.
trusted_origin = _is_trusted_torrent_fetch_url(url)
# tracker link, or Prowlarr reached through a separate proxy), and a trusted
# Prowlarr download URL commonly redirects to the indexer's own download
# link. We still need to fetch the .torrent to recover the info_hash when
# the source did not provide one, so the prefetch runs regardless of origin
# and follows cross-origin redirects. The Prowlarr API key, however, is
# re-evaluated per hop and only ever sent to a trusted origin so it can
# never leak to an arbitrary indexer/tracker host.
headers: dict[str, str] = {"Accept": "application/x-bittorrent"}
if trusted_origin:
# TODO(shelfmark): Move this source-specific Prowlarr auth handling into a source hook.
api_key = str(config.get("PROWLARR_API_KEY", "") or "").strip()
if api_key:
headers["X-Api-Key"] = api_key
# TODO(shelfmark): Move this source-specific Prowlarr auth handling into a source hook.
api_key = str(config.get("PROWLARR_API_KEY", "") or "").strip()
if api_key:
headers["X-Api-Key"] = api_key
def resolve_url(current: str, location: str) -> str:
if not location:
@@ -119,46 +253,49 @@ def extract_torrent_info(
return urljoin(current, location)
try:
logger.debug("Fetching torrent file from: %s...", url[:80])
logger.debug("Fetching torrent file from: %s...", _safe_url(url))
# Use allow_redirects=False to handle magnet link redirects manually
# Some indexers redirect download URLs to magnet links
resp = requests.get(
url,
timeout=30,
allow_redirects=False,
headers=headers,
verify=get_ssl_verify(url),
)
# Redirects are followed manually: some indexers redirect download URLs
# to magnet links, and each hop must decide anew whether it may see the
# API key.
current_url = url
redirects_remaining = _TORRENT_FETCH_MAX_REDIRECTS
while True:
request_headers = dict(headers)
if not _is_trusted_torrent_fetch_url(current_url):
request_headers.pop("X-Api-Key", None)
# Check if this is a redirect to a magnet link
if resp.status_code in (301, 302, 303, 307, 308):
redirect_url = resolve_url(url, resp.headers.get("Location", ""))
resp = requests.get(
current_url,
timeout=30,
allow_redirects=False,
headers=request_headers,
verify=get_ssl_verify(current_url),
)
if resp.status_code not in (301, 302, 303, 307, 308):
break
redirect_url = resolve_url(current_url, resp.headers.get("Location", ""))
if redirect_url.startswith("magnet:"):
logger.debug("Download URL redirected to magnet link")
info_hash = extract_hash_from_magnet(redirect_url)
if not info_hash and expected_hash:
info_hash = expected_hash
return TorrentInfo(
info_hash=info_hash,
info_hash=extract_hash_from_magnet(redirect_url),
torrent_data=None,
is_magnet=True,
magnet_url=redirect_url,
)
if not _is_trusted_torrent_fetch_url(redirect_url):
logger.debug(
"Skipping torrent prefetch redirect to untrusted URL: %s...",
redirect_url[:80],
if redirects_remaining <= 0:
logger.warning("Too many redirects fetching torrent file: %s...", _safe_url(url))
return TorrentInfo(
info_hash=None,
torrent_data=None,
is_magnet=False,
fetch_error="too many redirects",
)
return TorrentInfo(info_hash=expected_hash, torrent_data=None, is_magnet=False)
# Not a magnet redirect, follow it manually
logger.debug("Following redirect to: %s...", redirect_url[:80])
resp = requests.get(
redirect_url,
timeout=30,
headers=headers,
verify=get_ssl_verify(redirect_url),
)
redirects_remaining -= 1
logger.debug("Following redirect to: %s...", _safe_url(redirect_url))
current_url = redirect_url
resp.raise_for_status()
torrent_data = resp.content
@@ -169,25 +306,25 @@ def extract_torrent_info(
text_content = torrent_data.decode("utf-8", errors="ignore").strip()
if text_content.startswith("magnet:"):
logger.debug("Download URL returned magnet link as response body")
info_hash = extract_hash_from_magnet(text_content)
if not info_hash and expected_hash:
info_hash = expected_hash
return TorrentInfo(
info_hash=info_hash,
info_hash=extract_hash_from_magnet(text_content),
torrent_data=None,
is_magnet=True,
magnet_url=text_content,
)
info_hash = extract_info_hash_from_torrent(torrent_data) or expected_hash
info_hash = extract_info_hash_from_torrent(torrent_data)
if info_hash:
logger.debug("Extracted hash from torrent file: %s", info_hash)
else:
logger.warning("Could not extract hash from torrent file")
return TorrentInfo(info_hash=info_hash, torrent_data=torrent_data, is_magnet=False)
except _TORRENT_FETCH_ERRORS as e:
logger.debug("Could not fetch torrent file: %s", e)
return TorrentInfo(info_hash=expected_hash, torrent_data=None, is_magnet=False)
# Exception messages can repeat the source or redirect URL, including its
# credentials; scrub them before logging or storing the reason.
message = _redact_urls_in_text(str(e))
logger.warning("Could not fetch torrent file: %s: %s", type(e).__name__, message)
return TorrentInfo(info_hash=None, torrent_data=None, is_magnet=False, fetch_error=message)
def _is_trusted_torrent_fetch_url(url: str) -> bool:
@@ -322,6 +459,56 @@ def extract_info_hash_from_torrent(torrent_data: bytes) -> str | None:
return None
def _decode_torrent_text(value: object) -> str | None:
if isinstance(value, bytes):
return value.decode("utf-8", errors="replace")
if isinstance(value, str):
return value
return None
def extract_file_list_from_torrent(torrent_data: bytes) -> list[PackFile] | None:
"""List the files a .torrent describes, release-relative, without downloading it.
Multi-file torrents nest every path under the torrent name (which becomes the
client's save folder); single-file torrents are just the named file.
"""
try:
decoded, _ = bencode_decode(torrent_data)
except _TORRENT_PARSE_ERRORS as e:
logger.debug("Failed to parse torrent file list: %s", e)
return None
if not isinstance(decoded, dict):
return None
info = decoded.get(b"info")
if not isinstance(info, dict):
return None
name = _decode_torrent_text(info.get(b"name")) or ""
raw_files = info.get(b"files")
if not isinstance(raw_files, list):
length = info.get(b"length")
if not name:
return None
return [PackFile(name, length if isinstance(length, int) else None)]
files: list[PackFile] = []
for entry in raw_files:
if not isinstance(entry, dict):
continue
raw_path = entry.get(b"path")
if not isinstance(raw_path, list):
continue
segments = [seg for seg in (_decode_torrent_text(part) for part in raw_path) if seg]
if not segments:
continue
if name:
segments.insert(0, name)
length = entry.get(b"length")
files.append(PackFile("/".join(segments), length if isinstance(length, int) else None))
return files
def extract_hash_from_magnet(magnet_url: str) -> str | None:
"""Extract info_hash from a magnet URL."""
if not magnet_url.startswith("magnet:"):
@@ -390,6 +390,30 @@ class TransmissionClient(DownloadClient):
else:
return True
def _get_torrent_labels(self, download_id: str) -> list[str]:
"""Return a torrent's current labels, preserving their order."""
torrent = self._client.get_torrent(download_id)
raw_labels = getattr(torrent, "labels", None) or []
return [str(label) for label in raw_labels if str(label)]
def set_category(self, download_id: str, category: str) -> bool:
"""Add the post-import label to a torrent, keeping labels set elsewhere."""
try:
existing_labels = self._get_torrent_labels(download_id)
if category in existing_labels:
logger.debug(
"Transmission torrent %s already has label '%s'", download_id, category
)
return True
self._client.change_torrent(ids=download_id, labels=[*existing_labels, category])
logger.info("Added Transmission label '%s' to %s", category, download_id)
except _TRANSMISSION_CLIENT_ERRORS as e:
self._log_error("set_category", e)
return False
else:
return True
def get_download_path(self, download_id: str) -> str | None:
"""Get the path where torrent files are located.
+158
View File
@@ -0,0 +1,158 @@
"""RFC 8484 DNS wireformat encoding/decoding for DoH providers.
Providers split into two incompatible camps and the difference is not cosmetic:
* **JSON** (Cloudflare, Google) - ``?name=<host>&type=A`` returning a JSON body. A
convention, not a standard, and the only one Shelfmark used to speak.
* **Wireformat** (Quad9, OpenDNS) - RFC 8484 proper: a base64url-encoded DNS message
in ``?dns=``, answered with ``application/dns-message``. Quad9 additionally
*requires HTTP/2* per RFC 8484 section 5.2 and answers HTTP/1.1 with 505.
This module carries the codec only; the transport choice lives in the resolver.
Encoding a query is a handful of bytes, and parsing an answer needs message
compression support (RFC 1035 section 4.1.4) because answer names are almost always
pointers back into the question.
"""
from __future__ import annotations
import base64
import secrets
import struct
# Record types we resolve.
TYPE_A = 1
TYPE_AAAA = 28
_CLASS_IN = 1
_HEADER = struct.Struct(">HHHHHH")
_RR_FIXED = struct.Struct(">HHIH") # type, class, ttl, rdlength
_FLAG_RECURSION_DESIRED = 0x0100
_MAX_LABEL_JUMPS = 64 # cap pointer-following so a malicious answer cannot loop
_MAX_NAME_LENGTH = 255
class WireformatError(ValueError):
"""Raised when a DNS wireformat message cannot be parsed."""
def encode_query(hostname: str, record_type: int) -> bytes:
"""Build a DNS query message for ``hostname``.
The ID is zero because RFC 8484 section 4.1 requires it for cacheability, but the
caller may randomise it when not using a cache.
"""
if not hostname:
msg = "hostname must not be empty"
raise WireformatError(msg)
question = bytearray()
for label in hostname.rstrip(".").split("."):
encoded = label.encode("idna") if not label.isascii() else label.encode("ascii")
if not encoded or len(encoded) > 63:
msg = f"invalid DNS label in {hostname!r}"
raise WireformatError(msg)
question.append(len(encoded))
question.extend(encoded)
question.append(0)
question.extend(struct.pack(">HH", record_type, _CLASS_IN))
header = _HEADER.pack(0, _FLAG_RECURSION_DESIRED, 1, 0, 0, 0)
return header + bytes(question)
def encode_query_param(hostname: str, record_type: int) -> str:
"""Return the base64url ``dns=`` parameter value for a query (padding stripped)."""
return base64.urlsafe_b64encode(encode_query(hostname, record_type)).rstrip(b"=").decode()
def _read_name(message: bytes, offset: int) -> int:
"""Skip over a (possibly compressed) name, returning the offset after it."""
jumps = 0
length = 0
while True:
if offset >= len(message):
msg = "truncated DNS name"
raise WireformatError(msg)
label_len = message[offset]
if label_len == 0:
return offset + 1
if label_len & 0xC0 == 0xC0:
# A pointer ends this name; the rest of the record follows the 2 bytes.
if offset + 1 >= len(message):
msg = "truncated DNS name pointer"
raise WireformatError(msg)
return offset + 2
offset += 1 + label_len
length += 1 + label_len
jumps += 1
if jumps > _MAX_LABEL_JUMPS or length > _MAX_NAME_LENGTH:
msg = "malformed DNS name"
raise WireformatError(msg)
def decode_answer(message: bytes, record_type: int) -> list[str]:
"""Extract the IP addresses of ``record_type`` from a DNS response message.
Returns an empty list for a well-formed response that carries no matching record
(NXDOMAIN, or only CNAMEs), and raises WireformatError for a malformed one - the
caller treats those differently.
"""
if len(message) < _HEADER.size:
msg = "DNS response shorter than its header"
raise WireformatError(msg)
_id, _flags, qdcount, ancount, _ns, _ar = _HEADER.unpack_from(message, 0)
offset = _HEADER.size
for _ in range(qdcount):
offset = _read_name(message, offset)
offset += 4 # QTYPE + QCLASS
results: list[str] = []
for _ in range(ancount):
offset = _read_name(message, offset)
if offset + _RR_FIXED.size > len(message):
msg = "truncated resource record"
raise WireformatError(msg)
rtype, rclass, _ttl, rdlength = _RR_FIXED.unpack_from(message, offset)
offset += _RR_FIXED.size
rdata = message[offset : offset + rdlength]
if len(rdata) != rdlength:
msg = "truncated record data"
raise WireformatError(msg)
offset += rdlength
if rclass != _CLASS_IN or rtype != record_type:
continue
if rtype == TYPE_A and rdlength == 4:
results.append(".".join(str(b) for b in rdata))
elif rtype == TYPE_AAAA and rdlength == 16:
groups = struct.unpack(">8H", rdata)
results.append(_compress_ipv6(groups))
return results
def _compress_ipv6(groups: tuple[int, ...]) -> str:
"""Render an IPv6 address with the longest zero run collapsed to '::'."""
best_start = best_len = -1
run_start = -1
for i, group in enumerate([*list(groups), 1]): # sentinel closes a trailing run
if group == 0 and i < len(groups):
if run_start < 0:
run_start = i
elif run_start >= 0:
if i - run_start > best_len:
best_start, best_len = run_start, i - run_start
run_start = -1
parts = [format(g, "x") for g in groups]
if best_len > 1:
return ":".join(parts[:best_start]) + "::" + ":".join(parts[best_start + best_len :])
return ":".join(parts)
def random_query_id() -> int:
"""A random DNS message ID, for callers that do not want the RFC 8484 zero."""
return secrets.randbelow(0x10000)
+153 -6
View File
@@ -4,6 +4,7 @@ These utilities handle file collisions atomically, avoiding TOCTOU race conditio
when multiple workers may try to write to the same path simultaneously.
"""
import contextlib
import errno
import os
import shutil
@@ -104,6 +105,57 @@ _PUBLISH_VERIFY_RETRY_SECONDS = 0.25
_TEMPFILE_PREFIX = ".shelfmark."
_TEMPFILE_SUFFIX = ".tmp"
# Destinations that accept writes but reject unlink/rename, e.g. a Synology share
# with "Delete subfolders and files" unticked. Publishing a temp file into place
# removes a directory entry, so those paths must be written in place instead.
_DELETE_DENIED_DIRS: set[str] = set()
class _PublishDeniedError(Exception):
"""A fully-written temp file could not be renamed onto its final path."""
def _is_delete_denied_error(error: Exception) -> bool:
return isinstance(error, OSError) and error.errno in {errno.EACCES, errno.EPERM}
def mark_delete_denied(directory: Path) -> None:
"""Record that `directory` rejects deletes so later writes skip the temp file."""
key = str(directory)
if key in _DELETE_DENIED_DIRS:
return
_DELETE_DENIED_DIRS.add(key)
logger.warning(
"Destination %s rejects delete/rename; writing files in place instead of "
"publishing atomically. Grant delete permission to restore atomic writes.",
directory,
)
def clear_delete_denied(directory: Path) -> None:
"""Forget recorded denials for `directory` and anything beneath it.
Subdirectories get marked independently (an `organize` layout publishes into
per-author folders), so clearing only the exact key would leave a fixed
destination writing in place until restart.
"""
if not _DELETE_DENIED_DIRS:
return
key = str(directory)
prefix = f"{key}{os.sep}"
_DELETE_DENIED_DIRS.difference_update(
{marked for marked in _DELETE_DENIED_DIRS if marked == key or marked.startswith(prefix)}
)
def is_delete_denied(directory: Path) -> bool:
"""True if `directory` or one of its ancestors is known to reject deletes."""
if not _DELETE_DENIED_DIRS:
return False
if str(directory) in _DELETE_DENIED_DIRS:
return True
return any(str(parent) in _DELETE_DENIED_DIRS for parent in directory.parents)
def _verify_transfer_size(
dest: Path,
@@ -361,10 +413,40 @@ def _create_temp_path(dest_path: Path) -> Path:
return Path(temp_path)
def _discard_path(path: Path) -> None:
"""Best-effort unlink that tolerates destinations which reject deletes."""
try:
run_blocking_io(path.unlink, missing_ok=True)
except OSError as exc:
logger.warning("Could not remove %s: %s", path, exc)
def _copy_into_claimed(source_path: Path, dest_path: Path, expected_size: int) -> None:
"""Copy content straight into an already-claimed destination path.
Used when the destination rejects rename/unlink: there is no temp file to
publish, so the final name is written in place. This is not atomic - a
watcher can observe a partial file - but it is the only way to deliver on
such a share. `copyfile` (not `copy2`) because metadata copying needs chmod,
which those shares also tend to refuse.
"""
try:
run_blocking_io(shutil.copyfile, str(source_path), str(dest_path))
_verify_transfer_size(dest_path, expected_size, "copy")
except Exception:
with contextlib.suppress(OSError):
run_blocking_io(dest_path.unlink, missing_ok=True)
raise
def _publish_temp_file(temp_path: Path, dest_path: Path) -> bool:
"""Publish a temp file to its final path without overwriting existing files.
Returns True on success, False if the destination already exists.
Raises `_PublishDeniedError` when the rename is refused for lack of delete
permission. The claimed destination is left in place so the caller can write
into it directly instead.
"""
claimed = _claim_destination(dest_path)
if not claimed:
@@ -374,7 +456,19 @@ def _publish_temp_file(temp_path: Path, dest_path: Path) -> bool:
# Publish by renaming the fully-written temp file into place. This gives
# watchers an IN_MOVED_TO-style event on the final path instead of relying
# on hardlink support in the destination filesystem.
run_blocking_io(os.replace, str(temp_path), str(dest_path))
try:
run_blocking_io(os.replace, str(temp_path), str(dest_path))
except OSError as e:
if _is_delete_denied_error(e):
log_transfer_permission_context(
"publish_replace",
source=temp_path,
dest=dest_path,
error=e,
)
mark_delete_denied(dest_path.parent)
raise _PublishDeniedError(str(e)) from e
raise
# Best-effort nudge for watchers that only react to close-write on the
# final filename rather than rename/move events.
@@ -383,6 +477,8 @@ def _publish_temp_file(temp_path: Path, dest_path: Path) -> bool:
run_blocking_io(os.close, fd)
except OSError:
pass
except _PublishDeniedError:
raise
except Exception as e:
if _is_permission_error(e):
log_transfer_permission_context(
@@ -391,12 +487,23 @@ def _publish_temp_file(temp_path: Path, dest_path: Path) -> bool:
dest=dest_path,
error=e,
)
run_blocking_io(dest_path.unlink, missing_ok=True)
_discard_path(dest_path)
raise
else:
return True
def _move_via_copy(source_path: Path, dest_path: Path, max_attempts: int) -> Path:
"""Deliver a move as copy + source unlink.
For destinations that reject rename. The source lives in TMP_DIR (which we
own and can delete), so only the destination-side semantics change.
"""
final_path = atomic_copy(source_path, dest_path, max_attempts=max_attempts)
_discard_path(source_path)
return final_path
def atomic_move(source_path: Path, dest_path: Path, max_attempts: int = 100) -> Path:
"""Move a file with collision detection.
@@ -423,6 +530,11 @@ def atomic_move(source_path: Path, dest_path: Path, max_attempts: int = 100) ->
ext = dest_path.suffix
parent = dest_path.parent
# rename() removes a directory entry, so a destination that refuses deletes
# cannot be moved into. Deliver it as copy + source unlink instead.
if is_delete_denied(parent):
return _move_via_copy(source_path, dest_path, max_attempts)
for attempt in range(max_attempts):
try_path = dest_path if attempt == 0 else parent / f"{base}_{attempt}{ext}"
@@ -449,6 +561,13 @@ def atomic_move(source_path: Path, dest_path: Path, max_attempts: int = 100) ->
run_blocking_io(try_path.unlink, missing_ok=True)
continue
except OSError as e:
if _is_delete_denied_error(e):
# Destination refuses the rename; fall back to copy + unlink source.
mark_delete_denied(parent)
if claimed:
_discard_path(try_path)
return _move_via_copy(source_path, dest_path, max_attempts)
# Cross-filesystem - copy to temp and publish atomically.
if e.errno != errno.EXDEV:
if claimed:
@@ -497,7 +616,7 @@ def atomic_move(source_path: Path, dest_path: Path, max_attempts: int = 100) ->
try:
_verify_published_file(try_path, expected_size, "move")
except Exception:
run_blocking_io(try_path.unlink, missing_ok=True)
_discard_path(try_path)
raise
run_blocking_io(source_path.unlink)
@@ -508,9 +627,18 @@ def atomic_move(source_path: Path, dest_path: Path, max_attempts: int = 100) ->
if temp_path:
run_blocking_io(temp_path.unlink, missing_ok=True)
continue
except _PublishDeniedError:
# Destination is claimed but unrenameable; write into it directly.
_copy_into_claimed(source_path, try_path, expected_size)
if temp_path:
_discard_path(temp_path)
_discard_path(source_path)
if attempt > 0:
logger.info("File collision resolved: %s", try_path.name)
return try_path
except Exception:
if temp_path:
run_blocking_io(temp_path.unlink, missing_ok=True)
_discard_path(temp_path)
raise
else:
return try_path
@@ -629,6 +757,17 @@ def atomic_copy(source_path: Path, dest_path: Path, max_attempts: int = 100) ->
try_path = dest_path if attempt == 0 else parent / f"{base}_{attempt}{ext}"
if run_blocking_io(try_path.exists):
continue
# Known-undeletable destination: skip the temp file entirely, otherwise
# every transfer would strand a `.shelfmark.*.tmp` we cannot clean up.
if is_delete_denied(parent):
if not _claim_destination(try_path):
continue
_copy_into_claimed(source_path, try_path, expected_size)
if attempt > 0:
logger.info("File collision resolved: %s", try_path.name)
return try_path
temp_path: Path | None = None
try:
temp_path = _create_temp_path(try_path)
@@ -680,14 +819,22 @@ def atomic_copy(source_path: Path, dest_path: Path, max_attempts: int = 100) ->
try:
_verify_published_file(try_path, expected_size, "copy")
except Exception:
run_blocking_io(try_path.unlink, missing_ok=True)
_discard_path(try_path)
raise
if attempt > 0:
logger.info("File collision resolved: %s", try_path.name)
except _PublishDeniedError:
# The destination is claimed but unrenameable; write into it directly.
_copy_into_claimed(source_path, try_path, expected_size)
if temp_path:
_discard_path(temp_path)
if attempt > 0:
logger.info("File collision resolved: %s", try_path.name)
return try_path
except Exception:
if temp_path:
run_blocking_io(temp_path.unlink, missing_ok=True)
_discard_path(temp_path)
raise
else:
return try_path
+484 -83
View File
@@ -4,44 +4,62 @@ import random
import time
from http import HTTPStatus
from io import BytesIO
from threading import Event, Thread
from typing import TYPE_CHECKING, NoReturn
from urllib.parse import urljoin, urlparse
from urllib.parse import parse_qsl, urlencode, urljoin, urlparse, urlunparse
import requests
from tqdm import tqdm
from shelfmark.bypass import BypassCancelledError
from shelfmark.bypass import BypassCancelledError, ChallengeNotSolvedError, cookie_store
from shelfmark.bypass.challenge import challenge_marker
from shelfmark.bypass.waiting_room import WaitingRoomTimeoutError, is_aa_waiting_room
from shelfmark.core import search_deadline
from shelfmark.core.config import config as app_config
from shelfmark.core.logger import setup_logger
from shelfmark.core.request_helpers import coerce_bool, normalize_positive_int
from shelfmark.download import network
from shelfmark.download.activity import release_activity_grace, request_activity_grace
from shelfmark.download.network import get_proxies, get_ssl_verify
if TYPE_CHECKING:
from collections.abc import Callable
from threading import Event
from types import ModuleType
logger = setup_logger(__name__)
_RNG = random.SystemRandom()
_MAX_REDIRECTS = 5
# DDoS-Guard's re-check probe. Its 302 to `?check=1` is one hop of a handshake rather
# than a page: the parameter asserts the caller already holds the cookies that hop
# issued.
_DDG_CHECK_PARAM = "check"
# Z-Library answers the first hit with a 503 whose only real payload is a Set-Cookie; echoing
# that cookie back returns the 302 to the real page. Two attempts cover the handshake without
# letting a server that keeps re-issuing cookies hold us in the loop.
_MAX_COOKIE_HANDSHAKE_RETRIES = 2
_HTTP_STATUS_FORBIDDEN = HTTPStatus.FORBIDDEN
_HTTP_STATUS_NOT_FOUND = HTTPStatus.NOT_FOUND
_HTTP_STATUS_RATE_LIMITED = HTTPStatus.TOO_MANY_REQUESTS
_HTTP_STATUS_SERVICE_UNAVAILABLE = HTTPStatus.SERVICE_UNAVAILABLE
_HTTP_STATUS_OK = HTTPStatus.OK
_HTTP_STATUS_RANGE_NOT_SATISFIABLE = HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE
_HTTP_STATUS_PARTIAL_CONTENT = HTTPStatus.PARTIAL_CONTENT
_HTTP_STATUS_NON_RETRYABLE = (_HTTP_STATUS_FORBIDDEN, _HTTP_STATUS_NOT_FOUND)
_STATUS_CALLBACK_ERRORS = (AttributeError, KeyError, OSError, RuntimeError, TypeError, ValueError)
# Added on top of the active bypasser's own budget so it reports its real failure before
# stall detection cancels the download.
_BYPASS_GRACE_SLACK_SECONDS = 30.0
_BYPASSER_ERRORS = (
AttributeError,
BypassCancelledError,
ChallengeNotSolvedError,
KeyError,
OSError,
RuntimeError,
TypeError,
ValueError,
network.RateLimitedError,
requests.exceptions.RequestException,
)
@@ -54,6 +72,18 @@ def _raise_too_many_redirects(message: str) -> NoReturn:
raise requests.exceptions.TooManyRedirects(message)
def _new_cookies(response: requests.Response, already_sent: dict[str, str]) -> dict[str, str]:
"""Cookies a response set that we were not already echoing back.
Returning only the *new* ones is what makes the retry terminate: a server that keeps
re-issuing the same cookie yields nothing here, so we stop instead of spinning.
"""
jar = getattr(response, "cookies", None)
if not jar:
return {}
return {name: value for name, value in jar.items() if already_sent.get(name) != value}
def _get_internal_bypasser() -> ModuleType:
"""Lazy import of internal bypasser module."""
global _internal_bypasser
@@ -99,6 +129,19 @@ def _is_cf_bypass_enabled() -> bool:
return coerce_bool(app_config.get("USE_CF_BYPASS", True))
def _bypass_grace_seconds() -> float:
"""How long a bypass may block before stall detection should give up on it.
Each bypasser knows its own retry/timeout budget, so ask the active one rather than
duplicating the arithmetic here. The slack keeps the bypasser's own deadline expiring
first, so the user sees its real error instead of a generic "Download stalled".
"""
bypasser = (
_get_external_bypasser() if _is_using_external_bypasser() else _get_internal_bypasser()
)
return bypasser.max_duration_seconds() + _BYPASS_GRACE_SLACK_SECONDS
def get_bypassed_page(
url: str,
selector: network.AAMirrorSelector | None = None,
@@ -111,19 +154,13 @@ def get_bypassed_page(
def get_cf_cookies_for_domain(domain: str) -> dict[str, str]:
"""Get CF cookies - only available with internal bypasser."""
if _is_using_external_bypasser():
logger.debug("External bypasser in use, CF cookies not available for %s", domain)
return {}
return _get_internal_bypasser().get_cf_cookies_for_domain(domain)
"""Get the clearance cookies won by whichever bypasser solved this domain."""
return cookie_store.get_cf_cookies_for_domain(domain)
def get_cf_user_agent_for_domain(domain: str) -> str | None:
"""Get CF user agent - only available with internal bypasser."""
if _is_using_external_bypasser():
logger.debug("External bypasser in use, CF user agent not available for %s", domain)
return None
return _get_internal_bypasser().get_cf_user_agent_for_domain(domain)
"""Get the User-Agent that solved this domain's challenge, if one is stored."""
return cookie_store.get_cf_user_agent_for_domain(domain)
def _apply_cf_bypass(url: str, headers: dict) -> dict:
@@ -200,19 +237,99 @@ def _is_retryable_error(e: Exception) -> bool:
return status is not None and status in RETRYABLE_CODES
# Statuses that mean the host is gone rather than busy: 410 Gone and 451 Unavailable
# For Legal Reasons are what a seized domain answers with.
_DEAD_MIRROR_CODES = (410, 451)
def _response_challenge_marker(response: requests.Response) -> str | None:
"""The challenge marker in a response body, or None if it carries no challenge.
Content type is checked first so a JSON or octet-stream error body is never
decoded just to be scanned; a missing header is scanned anyway, since an
interstitial served without one is still an interstitial.
"""
content_type = response.headers.get("Content-Type", "")
if content_type and "html" not in content_type.lower():
return None
try:
return challenge_marker(response.text)
except UnicodeDecodeError, ValueError:
return None
def _solvable_url(url: str) -> str:
"""The URL a solver should open, given one we may be mid-handshake on.
The manual AA redirect follower in `html_get_page` walks DDoS-Guard's handshake by
reassigning `current_url`, so by the time a 403, a 503 challenge or a redirect loop
hands that URL to a bypasser it is often the `?check=1` probe rather than the page
we actually wanted. A solver opens it in a fresh browser holding none of the cookies
the probe exists to collect, so DDoS-Guard cannot verify it automatically and answers
with the manual CAPTCHA page that nothing can solve - the failure in #1292, where
FlareSolverr reported "Challenge solved!" over a 4.7 KB DDOS-GUARD interstitial.
Handing over the pre-probe URL instead lets the solver's browser run the whole
handshake itself, which is what a real browser does and what the solver is for.
Scoped to the hosts whose redirects we follow manually: everywhere else `check` is
an ordinary query parameter and none of our business.
"""
if not network.should_rotate_dns_for_url(url):
return url
parsed = urlparse(url)
params = parse_qsl(parsed.query, keep_blank_values=True)
kept = [(key, value) for key, value in params if key != _DDG_CHECK_PARAM]
if len(kept) == len(params):
return url
return urlunparse(parsed._replace(query=urlencode(kept)))
def _fatal_mirror_reason(e: Exception) -> str | None:
"""Return why ``e`` proves the mirror is unusable, or None if it may recover.
Hard evidence only - the name does not resolve, nothing is listening, or the host
says it is gone for good. A timeout, a 5xx or a challenge all mean the mirror is
alive, and rotating off it discards the bypass clearance held for that domain.
"""
status = _get_status_code(e)
if status is not None and status in _DEAD_MIRROR_CODES:
return f"HTTP {status}"
# requests wraps the real cause; a read timeout subclasses ConnectionError for
# some adapters, so exclude timeouts explicitly before inspecting the message.
if isinstance(e, requests.exceptions.Timeout):
return None
if not isinstance(e, requests.exceptions.ConnectionError):
return None
text = str(e).lower()
if "nameresolutionerror" in text or "failed to resolve" in text or "name or service" in text:
return "DNS does not resolve"
if "connection refused" in text or "no route to host" in text:
return "connection refused"
return None
def _try_rotation(
original_url: str, current_url: str, selector: network.AAMirrorSelector
original_url: str,
current_url: str,
selector: network.AAMirrorSelector,
*,
fatal_reason: str | None = None,
) -> str | None:
"""Try mirror/DNS rotation. Returns new URL or None."""
aa_base_url = network.get_aa_base_url()
if aa_base_url and current_url.startswith(aa_base_url):
new_base, action = selector.next_mirror_or_rotate_dns()
new_base, action = selector.next_mirror_or_rotate_dns(
fatal=fatal_reason is not None, reason=fatal_reason or ""
)
if action in ("mirror", "dns") and new_base:
new_url = selector.rewrite(original_url)
logger.info("[%s] switching to: %s", action, new_url)
logger.info("[%s] switching mirror", action)
return new_url
elif network.should_rotate_dns_for_url(current_url) and network.rotate_dns_provider():
logger.info("[dns-rotate] retrying: %s", original_url)
logger.info("[dns-rotate] retrying download")
return original_url
return None
@@ -238,8 +355,11 @@ def html_get_page(
selector: Mirror selector used for AA mirror and DNS rotation.
cancel_flag: Optional event used to abort retries early.
status_callback: Optional callback for UI status updates.
allow_bypasser_fallback: If False, 403 errors will trigger mirror rotation
instead of switching to the bypasser. Use for search operations.
allow_bypasser_fallback: Whether a challenge may be handed to the bypasser.
If False, a 403 triggers mirror rotation instead, and an AA redirect loop
gives up immediately rather than waiting on a browser solve. Use False for
best-effort fetches whose result is optional (e.g. the download count on
the details modal); search and detail pages pass True.
use_bypasser: Whether to start with the bypasser instead of direct HTTP.
include_response_url: If True, return `(html, final_url)` to expose the
resolved response URL after redirects.
@@ -248,59 +368,175 @@ def html_get_page(
"""
# Normalise before the closures below capture it: they touch selector.last_failure,
# so it must be a concrete selector, not the Optional parameter.
selector = selector or network.AAMirrorSelector()
# A release search runs under a wall-clock budget (see shelfmark.core.search_deadline).
# Adopting it as the cancel flag is what makes the budget bite on a solve already in
# flight: the bypassers and the helper subprocess poll this flag but know nothing about
# deadlines. Only when the caller has no flag of its own - a queued download brings one
# and must keep it, and runs outside any search context anyway.
if cancel_flag is None:
cancel_flag = search_deadline.cancel_event()
def _result(html: str, response_url: str) -> str | tuple[str, str]:
if include_response_url:
return html, response_url
return html
def _fail(reason: str, response_url: str) -> str | tuple[str, str]:
"""Record why the fetch is giving up, then return the empty result.
Every give-up path returns an empty page, which is all the caller used to
see. Stashing the concrete reason on the shared selector lets the caller
surface it (see release_sources.direct_download) rather than reporting the
same generic "network restricted or mirrors blocked" for every cause.
"""
selector.last_failure = reason
return _result("", response_url)
def _run_bypasser(bypass_url: str) -> str | tuple[str, str]:
"""Run the active bypasser for one URL and return its result.
Factored out so the redirect-loop handoff below can invoke it directly. That
call site sits inside the inner redirect `while`, so it cannot reach the
retry-loop branch above with `continue`, and with MAX_RETRY=1 there is no
later attempt for that branch to run on either.
"""
# Every handoff reaches the solver through here, so this is the one place the
# mid-handshake `?check=1` URL has to be unwound. See _solvable_url.
bypass_url = _solvable_url(bypass_url)
# Never start a minutes-long browser solve on a budget that has already run out:
# nothing downstream would get to report the real reason before the caller's
# deadline (or its reverse proxy) cut the request off.
if search_deadline.expired():
logger.info("Release search budget spent; not starting a bypass for %s", bypass_url)
return _fail(search_deadline.deadline_message(), bypass_url)
if status_callback:
status_callback("resolving", "Bypassing protection...")
try:
# A bypass is one long blocking call with no incremental progress, so
# tell the orchestrator up front how long it may legitimately take
# instead of trying to fake activity while it runs. Inside the try so a
# bypasser that fails to load is still reported as a bypasser error.
request_activity_grace(status_callback, _bypass_grace_seconds())
result = get_bypassed_page(bypass_url, selector, cancel_flag)
if result:
return _result(result, bypass_url)
return _fail(
"The protection bypasser returned an empty page — the challenge was "
"not solved. Check that FlareSolverr/the CF bypasser is reachable.",
bypass_url,
)
except network.RateLimitedError as e:
# Not a bypasser malfunction: the host is throttling this IP and a solve
# cannot help. Surface the wait as a plain failure so the search ends cleanly
# instead of looping another minutes-long solve against a 429.
logger.info("Skipping bypass (rate-limited): %s", e)
if status_callback:
try:
status_callback("resolving", "Rate limited, try again shortly")
except _STATUS_CALLBACK_ERRORS:
logger.debug("Rate-limit status callback failed", exc_info=True)
return _fail(str(e), bypass_url)
except WaitingRoomTimeoutError as e:
logger.info("Waiting room timed out: %s", e)
return _fail(str(e), bypass_url)
except ChallengeNotSolvedError as e:
# Not a bypasser malfunction: it ran, and the host answered with something it
# cannot clear - DDoS-Guard's manual CAPTCHA, typically. Must precede the
# generic handler below, whose "the protection bypasser failed" is what sent
# #1292 off to fix a FlareSolverr that was working perfectly.
logger.info("Bypass ran but did not clear the protection: %s", e)
if status_callback:
try:
status_callback("error", str(e))
except _STATUS_CALLBACK_ERRORS:
logger.debug("Unsolved-challenge status callback failed", exc_info=True)
return _fail(str(e), bypass_url)
except _BYPASSER_ERRORS as e:
logger.warning("Bypasser error: %s: %s", type(e).__name__, e)
# Surface the real reason. Without this the caller only sees an empty
# page and the download dies with a generic failure, hiding e.g. a
# FlareSolverr 500 behind a silent wait.
if status_callback and not isinstance(e, BypassCancelledError):
try:
status_callback("error", f"Bypass failed: {type(e).__name__}: {e}")
except _STATUS_CALLBACK_ERRORS:
logger.debug("Bypass error status callback failed", exc_info=True)
if isinstance(e, BypassCancelledError):
# The budget trips the same cancel flag a user's cancel does, so tell them
# apart here - "cancelled" is a confusing thing to read when nobody did.
if search_deadline.expired():
return _fail(search_deadline.deadline_message(), bypass_url)
return _fail("The protection bypass was cancelled.", bypass_url)
return _fail(f"The protection bypasser failed: {type(e).__name__}: {e}", bypass_url)
finally:
release_activity_grace(status_callback)
def _bypass_handoff_allowed() -> bool:
"""Whether a challenge on the current URL may be handed to the bypasser.
allow_bypasser_fallback is honoured for the same reason the 403 path honours it:
callers such as the /dyn/md5/summary fetch behind the details modal pass False
precisely so a best-effort request fails fast instead of holding the UI open for
a minutes-long browser solve.
"""
return allow_bypasser_fallback and _is_cf_bypass_enabled() and not use_bypasser_now
def _purge_clearance(target_url: str) -> None:
"""Drop the host's stored clearance cookies.
Called whenever the protection answered a request that *carried* cookies:
being challenged while presenting them proves they no longer work, so keeping
them only guarantees the same rejection on every later request. Applies to
either bypasser, since both fill the same store.
"""
hostname = urlparse(target_url).hostname or ""
# An empty domain means "clear every host" to the store, so skip the purge
# rather than wipe clearance for sites that are working fine.
if hostname:
cookie_store.clear_cf_cookies(hostname)
def _redirect_loop_handoff(bypass_url: str) -> str | tuple[str, str]:
"""Drop the host's stale clearance cookies, then bypass `bypass_url`.
A `?check=1` loop is how DDoS-Guard answers a clearance cookie that has gone
stale, so the dead cookie has to go before the solve — otherwise it is merged
back over the fresh one on the next request and the loop simply resumes.
"""
_purge_clearance(bypass_url)
return _run_bypasser(bypass_url)
configured_retry = normalize_positive_int(app_config.MAX_RETRY)
retry_limit = (
retry if retry is not None else (configured_retry if configured_retry is not None else 1)
)
selector = selector or network.AAMirrorSelector()
original_url = url
current_url = selector.rewrite(original_url)
use_bypasser_now = use_bypasser
# Survives across attempts so a cookie won once is still presented on later retries.
handshake_cookies: dict[str, str] = {}
handshake_retries = 0
# Last transport error seen, so the exhausted-retries path can name the real
# cause (timeout, connection refused, DNS, ...) instead of a generic message.
last_error: Exception | None = None
for attempt in range(1, retry_limit + 1):
# Check for cancellation before each attempt
if cancel_flag and cancel_flag.is_set():
if search_deadline.expired():
logger.info("Release search budget spent before attempt %s", attempt)
return _fail(search_deadline.deadline_message(), current_url)
logger.info("html_get_page cancelled before attempt %s", attempt)
return _result("", current_url)
return _fail("The request was cancelled.", current_url)
cookies: dict[str, str] = {}
try:
if use_bypasser_now and _is_cf_bypass_enabled():
if status_callback:
status_callback("resolving", "Bypassing protection...")
heartbeat_stop = Event()
heartbeat_thread: Thread | None = None
if status_callback:
def _heartbeat() -> None:
# Keep the download "alive" during long bypass operations so the orchestrator
# doesn't flag it as stalled.
if cancel_flag and cancel_flag.is_set():
return
try:
status_callback("resolving", "Bypassing protection...")
except _STATUS_CALLBACK_ERRORS:
return
heartbeat_thread = Thread(
target=_heartbeat, daemon=True, name="BypassHeartbeat"
)
heartbeat_thread.start()
try:
result = get_bypassed_page(current_url, selector, cancel_flag)
return _result(result or "", current_url)
except _BYPASSER_ERRORS as e:
logger.warning("Bypasser error: %s: %s", type(e).__name__, e)
return _result("", current_url)
finally:
heartbeat_stop.set()
if heartbeat_thread:
heartbeat_thread.join(timeout=1)
return _run_bypasser(current_url)
logger.debug("GET: %s", current_url)
@@ -322,12 +558,67 @@ def html_get_page(
current_url,
proxies=get_proxies(current_url),
timeout=REQUEST_TIMEOUT,
cookies=cookies,
# Handshake cookies win. They were issued by *this* exchange, so by
# definition they are fresher than anything the store holds, and the
# server is waiting to see them echoed back on the very next hop.
# Letting the store overwrite them meant a stored cookie of the same
# name (DDoS-Guard reuses __ddg1_/__ddg2_ for both) was replayed on
# every hop and the freshly issued value never left this process - the
# ?check=1 probe could then never terminate, so every request ended in
# the redirect-loop handoff and paid for a full browser solve.
cookies={**cookies, **handshake_cookies},
headers=headers,
allow_redirects=allow_redirects,
verify=get_ssl_verify(current_url),
)
# Z-Library gates the first hit with a 503 that carries nothing but a
# Set-Cookie; echoing it back yields the 302 to the real page. Without this
# the cookie is dropped and every retry re-runs the same rejected request.
if (
response.status_code == _HTTP_STATUS_SERVICE_UNAVAILABLE
and handshake_retries < _MAX_COOKIE_HANDSHAKE_RETRIES
):
issued = _new_cookies(response, handshake_cookies)
if issued:
handshake_cookies.update(issued)
handshake_retries += 1
logger.debug(
"503 set %s cookie(s); retrying with them: %s",
len(issued),
current_url,
)
continue
# A 503 still serving a challenge is protection, not a busy origin. The
# handshake above has nothing left to echo back, and 503 is in
# RETRYABLE_CODES, so without this the request spends every attempt on
# the same wall: the bypasser is only ever reached from the 403 branch
# and the AA redirect rescues. Gate on the body, not the status, so a
# genuine overloaded-origin 503 keeps its retry path.
if response.status_code == _HTTP_STATUS_SERVICE_UNAVAILABLE:
marker = _response_challenge_marker(response)
if marker and _bypass_handoff_allowed():
if cookies:
# Challenged while presenting clearance means those cookies
# are dead; same reasoning as the 403 branch below.
logger.debug(
"503 challenge with cookies presented; purging: %s", current_url
)
_purge_clearance(current_url)
logger.info(
"503 challenge detected (%s); switching to bypasser: %s",
marker,
current_url,
)
return _run_bypasser(current_url)
if marker:
logger.debug(
"503 challenge (%s) but no bypasser handoff available: %s",
marker,
current_url,
)
if is_aa_url and response.is_redirect:
location = response.headers.get("Location", "")
if not location:
@@ -349,13 +640,19 @@ def html_get_page(
redirect_host,
current_url,
)
return _result("", current_url)
return _fail(
f"The configured mirror {current_host} redirected to "
f"{redirect_host}; it may be down or seized. Point MIRROR at "
"a working host or switch to auto mode.",
current_url,
)
new_url = _try_rotation(original_url, current_url, selector)
if new_url:
current_url = new_url
# Reset per-request state for the new host.
headers = {"User-Agent": DOWNLOAD_HEADERS["User-Agent"]}
handshake_cookies.clear()
is_aa_url = network.should_rotate_dns_for_url(current_url)
allow_redirects = not is_aa_url
redirects_followed = 0
@@ -367,23 +664,82 @@ def html_get_page(
redirect_host,
current_url,
)
return _result("", current_url)
return _fail(
"Every Anna's Archive mirror redirected away to a dead host — "
"all configured mirrors are unreachable.",
current_url,
)
# Same-host redirect (relative or absolute) - follow manually.
# DDoS-Guard gates AA /search behind a cookie probe: the 302 to
# ?check=1 carries Set-Cookie (__ddg*) which must be echoed back on
# the next hop, or the server just re-issues the redirect forever.
issued = _new_cookies(response, handshake_cookies)
if issued:
handshake_cookies.update(issued)
redirects_followed += 1
if redirects_followed > _MAX_REDIRECTS:
_raise_too_many_redirects(f"Too many redirects for {current_url}")
# A same-host redirect loop on AA is not a network fault — it is
# how DDoS-Guard presents a handshake that is unsolved, or whose
# clearance cookie has gone stale: /search redirects to
# /search&check=1, which redirects back, indefinitely. Hand it
# straight to the bypasser rather than raising, which would send it
# down the retry path to re-run the whole loop on every attempt
# (10 x 6 = ~60 requests to AA) without ever offering the URL to the
# bypasser. `continue` is no use here either — it would target this
# inner redirect loop rather than the retry branch below.
if _bypass_handoff_allowed():
logger.info(
"Redirect loop detected; switching to bypasser: %s", current_url
)
return _redirect_loop_handoff(current_url)
# No bypasser to hand it to. Every AA mirror shares the challenge,
# so rotating only collects another loop — give up now instead of
# raising and burning the same ~60 requests over the retry budget.
logger.warning(
"Redirect loop and no bypasser available, giving up: %s", current_url
)
return _fail(
"Anna's Archive is behind a protection challenge (endless "
"redirect loop) and no bypasser is enabled to solve it. Enable "
"FlareSolverr/the CF bypasser.",
current_url,
)
current_url = redirect_url
continue
response.raise_for_status()
if (
_bypass_handoff_allowed()
and not _is_using_external_bypasser()
and is_aa_waiting_room(current_url, response.text)
):
# A successful HTTP response can still need a live browser: the
# queue's JavaScript must finish in the session that entered it.
return _run_bypasser(current_url)
if success_delay > 0:
time.sleep(success_delay)
return _result(response.text, response.url)
except Exception as e:
last_error = e
status = _get_status_code(e)
# The same DDoS-Guard rescue, for the loops the manual AA follower above hands
# back rather than resolving inline — an AA redirect missing its Location
# header. TooManyRedirects carries no status, so the 403 rescue below never
# fires and every retry would re-send the dead cookies. Scoped to the hosts
# whose redirects we follow manually: elsewhere `requests` follows them itself,
# and a loop there is an ordinary misconfiguration that a cookie purge and a
# minutes-long browser solve would be the wrong answer to.
if (
isinstance(e, requests.exceptions.TooManyRedirects)
and network.should_rotate_dns_for_url(current_url)
and _bypass_handoff_allowed()
):
logger.info("Redirect loop detected; switching to bypasser: %s", current_url)
return _redirect_loop_handoff(current_url)
# 403 = Cloudflare/DDoS-Guard protection
if status == _HTTP_STATUS_FORBIDDEN:
# If bypasser fallback is disabled, try mirrors instead
@@ -393,38 +749,73 @@ def html_get_page(
current_url = new_url
continue
logger.warning("403 error, mirrors exhausted: %s", current_url)
return _result("", current_url)
return _fail(
"Anna's Archive returned 403 (blocked) and all mirrors are exhausted.",
current_url,
)
if _is_cf_bypass_enabled() and not use_bypasser_now:
# Before switching to bypasser, check if cookies have become available
# (another concurrent download may have completed bypass and extracted cookies)
parsed = urlparse(current_url)
fresh_cookies = get_cf_cookies_for_domain(parsed.hostname or "")
if fresh_cookies and not cookies:
# Cookies are now available - retry with cookies before using bypasser
if fresh_cookies and not cookies and attempt < retry_limit:
# Cookies are now available - retry with cookies before using bypasser.
# Guarded on there being a next attempt: `continue` on the last one
# ends the retry loop and abandons the request without ever offering
# the URL to the bypasser, and MAX_RETRY=1 is the supported setting.
# Same reasoning as the bypasser invocation below.
logger.debug(
"403 but cookies now available - retrying with cookies: %s",
current_url,
)
continue
if cookies:
# Challenged *while presenting* clearance: those cookies are
# dead. Without this they survive the solve and get merged back
# over the fresh ones, so every later request re-presents a
# known-rejected cookie and is challenged again - the stale
# retry that never ends.
logger.debug("403 with cookies presented; purging: %s", current_url)
_purge_clearance(current_url)
logger.info("403 detected; switching to bypasser: %s", current_url)
if status_callback:
status_callback("resolving", "Bypassing protection...")
use_bypasser_now = True
continue
# Invoke it here rather than setting use_bypasser_now and continuing.
# The branch that acts on that flag runs at the top of the *next* retry
# attempt, so under the supported MAX_RETRY=1 there is no next attempt
# and the bypasser was never reached — a 403 simply ended the search.
# Same reasoning as the redirect-loop handoffs.
return _run_bypasser(current_url)
logger.warning("403 error, giving up: %s", current_url)
return _result("", current_url)
return _fail(
"Anna's Archive returned 403 (blocked) and no bypasser is enabled "
"to solve the protection challenge.",
current_url,
)
# 404 = Not found
if status == _HTTP_STATUS_NOT_FOUND:
logger.warning("404 error: %s", current_url)
return _result("", current_url)
return _fail(
f"Anna's Archive returned 404 Not Found for {current_url}.", current_url
)
# Try mirror/DNS rotation on retryable errors
if _is_retryable_error(e):
new_url = _try_rotation(original_url, current_url, selector)
# 429 = origin throttling this IP. Arm the per-host backoff so selection and
# the bypasser stop hammering it, then fall through to normal rotation onto a
# mirror that is not (yet) rate-limited.
if status == _HTTP_STATUS_RATE_LIMITED:
network.note_rate_limited(current_url)
# Try mirror/DNS rotation on retryable errors. A failure that proves the
# mirror is unusable also drops it from this process's rotation, so the
# next search does not pay for it again.
fatal_reason = _fatal_mirror_reason(e)
if fatal_reason or _is_retryable_error(e):
new_url = _try_rotation(
original_url, current_url, selector, fatal_reason=fatal_reason
)
if new_url:
current_url = new_url
handshake_cookies.clear()
continue
# Retry with backoff
@@ -441,7 +832,16 @@ def html_get_page(
else:
logger.exception("Giving up after %s attempts: %s", retry_limit, current_url)
return _result("", current_url)
if last_error is not None:
return _fail(
f"Could not reach Anna's Archive after {retry_limit} attempt(s): "
f"{type(last_error).__name__}: {last_error}",
current_url,
)
return _fail(
"Could not reach Anna's Archive — all mirrors were exhausted without a usable response.",
current_url,
)
def download_url(
@@ -480,12 +880,7 @@ def download_url(
f"Connecting (Attempt {attempt + 1}/{MAX_DOWNLOAD_RETRIES})",
)
logger.info(
"Downloading: %s (attempt %s/%s)",
current_url,
attempt + 1,
MAX_DOWNLOAD_RETRIES,
)
logger.info("Downloading (attempt %s/%s)", attempt + 1, MAX_DOWNLOAD_RETRIES)
# Try with CF cookies/UA if available
cookies = _apply_cf_bypass(current_url, headers)
response = requests.get(
@@ -523,7 +918,7 @@ def download_url(
and bytes_downloaded < total_size * 0.9
and response.headers.get("content-type", "").startswith("text/html")
):
logger.warning("Received HTML instead of file: %s", current_url)
logger.warning("Received HTML instead of file")
return None
logger.debug("Download completed: %s bytes", bytes_downloaded)
@@ -541,23 +936,27 @@ def download_url(
parsed = urlparse(current_url)
if _is_configured_zlib_host(parsed.hostname) and referer:
zlib_cookie_refresh_attempted = True
logger.info("Z-Library 403 - refreshing cookies via referer: %s", referer)
logger.info("Z-Library 403 - refreshing cookies via referer")
try:
get_bypassed_page(referer, selector, cancel_flag)
time.sleep(0.5)
# Retry with fresh cookies (don't increment attempt)
continue
except _BYPASSER_ERRORS as cookie_err:
logger.warning("Z-Library cookie refresh failed: %s", cookie_err)
logger.warning(
"Z-Library cookie refresh failed: %s",
type(cookie_err).__name__,
)
# Non-retryable errors
if status in _HTTP_STATUS_NON_RETRYABLE:
logger.warning("Download failed (%s): %s", status, current_url)
logger.warning("Download failed (%s)", status)
return None
# Rate limited - skip to next source immediately
# (waiting doesn't help with concurrent downloads hitting the same server)
if status == _HTTP_STATUS_RATE_LIMITED:
network.note_rate_limited(current_url)
logger.info("Rate limited (429) - trying next source")
if status_callback:
status_callback("resolving", "Server busy, trying next")
@@ -565,7 +964,7 @@ def download_url(
# Timeout - don't retry, server likely overloaded
if isinstance(e, requests.exceptions.Timeout):
logger.warning("Timeout: %s - skipping to next source", current_url)
logger.warning("Timeout - skipping to next source")
if status_callback:
status_callback("resolving", "Server timed out, trying next")
return None
@@ -592,14 +991,14 @@ def download_url(
attempt += 1
continue
logger.warning("Download error: %s: %s", type(e).__name__, e)
logger.warning("Download error: %s", type(e).__name__)
if attempt < MAX_DOWNLOAD_RETRIES - 1:
time.sleep(_backoff_delay(attempt + 1))
attempt += 1
else:
return buffer
logger.error("Download failed after %s attempts: %s", MAX_DOWNLOAD_RETRIES, link)
logger.error("Download failed after %s attempts", MAX_DOWNLOAD_RETRIES)
return None
@@ -689,7 +1088,7 @@ def _try_resume(
logger.info("Resume completed: %s bytes", start_byte)
except requests.exceptions.RequestException as e:
logger.debug("Resume attempt %s failed: %s", attempt + 1, e)
logger.debug("Resume attempt %s failed: %s", attempt + 1, type(e).__name__)
else:
return buffer
@@ -705,5 +1104,7 @@ def get_absolute_url(base_url: str, url: str) -> str:
parsed = urlparse(url)
base = urlparse(base_url)
if not parsed.netloc or not parsed.scheme:
parsed = parsed._replace(netloc=base.netloc, scheme=base.scheme)
parsed = parsed._replace(
netloc=parsed.netloc or base.netloc, scheme=parsed.scheme or base.scheme
)
return parsed.geturl()
+427 -55
View File
@@ -3,14 +3,16 @@
import fnmatch
import ipaddress
import socket
import time
import urllib.parse
import urllib.request
from datetime import UTC, datetime, timedelta
from http import HTTPStatus
from socket import AddressFamily, SocketKind
from typing import TYPE_CHECKING, Any, cast
from typing import TYPE_CHECKING, Any, NamedTuple, cast
import dns.resolver
import httpx
import requests
from dns.exception import DNSException
@@ -277,6 +279,116 @@ _current_aa_url_index = 0
_aa_urls: list[str] = [] # Initialized lazily in _initialize_aa_state()
_aa_base_url: str = "" # Current active AA URL
# Mirrors quarantined for this process: domains that are not a working AA mirror at
# all (NXDOMAIN, refused, or a 200 that isn't AA - seized/parked/for-sale domains all
# land here). Kept separate from ordinary failures: a 403 challenge or a 5xx means the
# mirror is alive and rotating away from it only discards the DDoS-Guard clearance we
# hold for it. Deliberately in-memory only, so a restart re-probes everything.
_dead_aa_urls: set[str] = set()
_dead_aa_urls_lock = _RLock()
# Per-host rate-limit backoff. A 429 is the origin throttling *this IP*, not a challenge:
# a DDoS-Guard/Cloudflare solve still renders, so the bypass "succeeds" yet the cleared
# request is rejected again and the throttle is only renewed. The single answer is to
# wait, so a 429 sidelines the host for a growing window - mirror selection and the
# bypasser both skip a cooling-down host until its deadline passes. The wait escalates
# 2 -> 5 -> 10 -> 15 -> 30 minutes each time the host throttles us again *after* we
# already waited a full window out; a host left clear for longer than the top step
# starts the ladder over. Keyed by host so every mirror and source shares one view;
# in-memory only, so a restart starts clean.
_RATE_LIMIT_COOLDOWN_LADDER_SECONDS: tuple[float, ...] = (120.0, 300.0, 600.0, 900.0, 1800.0)
# A host that has been clear this long is treated as a fresh episode: the next 429
# restarts the ladder at 2 minutes rather than resuming the escalation.
_RATE_LIMIT_RESET_AFTER_SECONDS = 1800.0
class _Cooldown(NamedTuple):
"""One host's active rate-limit window and how far up the ladder it has climbed."""
deadline: float # time.monotonic() value at which the wait expires
level: int # index into _RATE_LIMIT_COOLDOWN_LADDER_SECONDS
_host_cooldowns: dict[str, _Cooldown] = {}
_host_cooldowns_lock = _RLock()
class RateLimitedError(Exception):
"""Raised to abandon a request whose host is in a 429 cooldown.
Not a transport failure - nothing is wrong with the network, the origin is
throttling this IP and only time clears it. Callers surface it as a plain failure
rather than retrying or handing the URL to the bypasser.
"""
def _cooldown_key(url: str) -> str:
"""Host a cooldown is keyed by; '' when the URL carries none."""
return (urllib.parse.urlparse(url).hostname or "").lower()
def note_rate_limited(url: str) -> float:
"""Escalate a host's 429 backoff and (re)arm its cooldown; return the wait applied.
The step advances only when a fresh 429 arrives *after* the previous window already
elapsed - i.e. we waited it out and the host throttled us again. A 429 that lands
while the host is still cooling is the same episode: it neither escalates the level
nor shortens the wait. See the ladder note above.
"""
host = _cooldown_key(url)
if not host:
return 0.0
now = time.monotonic()
ladder = _RATE_LIMIT_COOLDOWN_LADDER_SECONDS
with _host_cooldowns_lock:
prev = _host_cooldowns.get(host)
if prev is not None and now < prev.deadline:
# Still inside the current window - same throttling episode, leave it be.
return prev.deadline - now
if prev is None or now - prev.deadline > _RATE_LIMIT_RESET_AFTER_SECONDS:
level = 0
else:
level = min(prev.level + 1, len(ladder) - 1)
wait = ladder[level]
_host_cooldowns[host] = _Cooldown(deadline=now + wait, level=level)
logger.info(
"Rate limited (429): backing off %s for %.0fs (step %d/%d)",
host,
wait,
level + 1,
len(ladder),
)
return wait
def host_cooldown_remaining(url: str) -> float:
"""Seconds left on a host's 429 cooldown; 0.0 when clear or expired.
Leaves an expired record in place: the ladder level it carries is what a later 429
escalates from (or resets, once the clear gap is long enough).
"""
host = _cooldown_key(url)
if not host:
return 0.0
now = time.monotonic()
with _host_cooldowns_lock:
rec = _host_cooldowns.get(host)
if rec is None or rec.deadline <= now:
return 0.0
return rec.deadline - now
def is_host_cooling_down(url: str) -> bool:
"""True while ``url``'s host is inside its 429 cooldown window."""
return host_cooldown_remaining(url) > 0.0
def clear_host_cooldowns() -> None:
"""Forget all rate-limit cooldowns (manual reset / tests)."""
with _host_cooldowns_lock:
_host_cooldowns.clear()
def _ensure_initialized() -> None:
"""Lazy guard so runtime setup happens once and late calls still work."""
@@ -298,6 +410,24 @@ DNS_PROVIDERS = [
("opendns", ["208.67.222.222", "208.67.220.220"], "https://doh.opendns.com/dns-query"),
]
# httpx raises its own hierarchy, which shares no base class with requests', so a
# wireformat failure would escape a requests-only except clause.
_DOH_REQUEST_ERRORS = (OSError, ValueError, requests.RequestException, httpx.HTTPError)
def _first_proxy(proxies: dict[str, str] | None) -> str | None:
"""Pick a single proxy URL from a requests-style mapping, for httpx."""
if not proxies:
return None
return proxies.get("https") or proxies.get("http") or None
# DoH providers that speak RFC 8484 wireformat rather than the (non-standard) JSON API
# Cloudflare and Google popularised. Verified against the live services: both reject a
# ?name=&type= query outright - Quad9 with 505 (it also mandates HTTP/2 per RFC 8484
# section 5.2, which requests cannot speak), OpenDNS with 400 "No valid query received".
_DOH_WIREFORMAT_HOSTS = frozenset({"dns.quad9.net", "doh.opendns.com"})
# Domain patterns that should trigger DNS rotation on failure
DNS_ROTATION_DOMAINS = [
"annas-archive",
@@ -462,8 +592,16 @@ class DoHResolver:
# DNS cache: {(hostname, record_type): (ip_list, timestamp)}
self._cache: dict[tuple[str, str], tuple[list[str], datetime]] = {}
# Different headers based on provider
if "google" in self.base_url:
# RFC 8484 providers get a separate transport: they need wireformat, and Quad9
# additionally refuses HTTP/1.1, which requests has no way to upgrade from.
self.use_wireformat = urllib.parse.urlparse(self.base_url).hostname in (
_DOH_WIREFORMAT_HOSTS
)
self._http2_client: Any | None = None
if self.use_wireformat:
self.session.headers.update({"Accept": "application/dns-message"})
elif "google" in self.base_url:
self.session.headers.update(
{
"Accept": "application/json",
@@ -476,6 +614,35 @@ class DoHResolver:
}
)
def _get_http2_client(self) -> Any:
"""Lazily build the HTTP/2 client used for RFC 8484 providers.
Built on first use so a resolver pointed at a JSON provider never opens an
HTTP/2 connection pool it will not use.
"""
if self._http2_client is None:
self._http2_client = httpx.Client(
http2=True,
timeout=10,
verify=get_ssl_verify(self.base_url),
proxy=_first_proxy(get_proxies(self.base_url)),
)
return self._http2_client
def _resolve_wireformat(self, hostname: str, record_type: str) -> list[str]:
"""Resolve via RFC 8484: base64url query in, DNS message out."""
from shelfmark.download import doh_wireformat
qtype = doh_wireformat.TYPE_AAAA if record_type == "AAAA" else doh_wireformat.TYPE_A
param = doh_wireformat.encode_query_param(hostname, qtype)
response = self._get_http2_client().get(
self.base_url,
params={"dns": param},
headers={"Accept": "application/dns-message"},
)
response.raise_for_status()
return doh_wireformat.decode_answer(response.content, qtype)
def _get_cached(self, hostname: str, record_type: str) -> list[str] | None:
"""Get cached DNS result if still valid."""
key = (hostname, record_type)
@@ -525,34 +692,37 @@ class DoHResolver:
return cached
try:
params = {"name": hostname, "type": "AAAA" if record_type == "AAAA" else "A"}
if self.use_wireformat:
answers = self._resolve_wireformat(hostname, record_type)
else:
params = {"name": hostname, "type": "AAAA" if record_type == "AAAA" else "A"}
response = self.session.get(
self.base_url,
params=params,
proxies=get_proxies(self.base_url),
timeout=10, # Increased from 5s to handle slow network conditions
verify=get_ssl_verify(self.base_url),
)
response.raise_for_status()
response = self.session.get(
self.base_url,
params=params,
proxies=get_proxies(self.base_url),
timeout=10, # Increased from 5s to handle slow network conditions
verify=get_ssl_verify(self.base_url),
)
response.raise_for_status()
data = response.json()
if "Answer" not in data:
logger.warning("DoH resolution failed for %s: %s", hostname, data)
return []
data = response.json()
if "Answer" not in data:
logger.warning("DoH resolution failed for %s: %s", hostname, data)
return []
# Extract IP addresses from the response
answers = [
answer["data"]
for answer in data["Answer"]
if answer.get("type") == (28 if record_type == "AAAA" else 1)
]
# Extract IP addresses from the response
answers = [
answer["data"]
for answer in data["Answer"]
if answer.get("type") == (28 if record_type == "AAAA" else 1)
]
# Cache the result
self._set_cached(hostname, record_type, answers)
# Don't log here - the caller (custom_getaddrinfo) will log the final result
except (OSError, ValueError, requests.RequestException) as e:
except _DOH_REQUEST_ERRORS as e:
logger.warning("DoH resolution failed for %s: %s", hostname, e)
return []
else:
@@ -616,8 +786,6 @@ def create_custom_getaddrinfo(
source: str,
provider_label: str,
res: Sequence[tuple[AddressFamily, SocketKind, int, str, tuple[Any, ...]]],
*,
is_bypass: bool = False,
) -> None:
"""Emit a unified resolver log with the IPs returned.
@@ -625,7 +793,6 @@ def create_custom_getaddrinfo(
source: Description of resolver source
provider_label: Label for the DNS provider
res: Resolution results
is_bypass: If True, log at DEBUG level (for local/IP addresses)
"""
# Skip logging entirely for localhost to reduce noise
@@ -641,11 +808,7 @@ def create_custom_getaddrinfo(
ip = sockaddr[0]
if isinstance(ip, str):
ips.append(ip)
msg = f"Resolved {host_str} via {source} [{provider_label}]: {ips}"
if is_bypass:
logger.debug(msg)
else:
logger.info(msg)
logger.debug("Resolved %s via %s [%s]: %s", host_str, source, provider_label, ips)
# Skip custom resolution for IP addresses, local addresses, or if skip check passes
if (
@@ -655,7 +818,7 @@ def create_custom_getaddrinfo(
):
# Quietly bypass custom resolution for IP/local targets
res = original_getaddrinfo(host, port, family, socket_type, proto, flags)
_log_results("system resolver (bypass)", "system", res, is_bypass=True)
_log_results("system resolver (bypass)", "system", res)
return res
results: list[tuple[AddressFamily, SocketKind, int, str, tuple[Any, ...]]] = []
@@ -852,6 +1015,99 @@ def _init_custom_resolver_internal(servers: list[str]) -> dns.resolver.Resolver:
return custom_resolver
# --- ISP / network DNS interference detection ---------------------------------
# Compare what the (tamperable) system resolver returns for a host against a
# tamper-resistant DoH lookup. Divergent answers are a strong signal the network is
# hijacking or NXDOMAIN-blocking the domain (a common reason AA downloads "work" but
# land on an ISP block page). Used to surface an actionable hint to the user.
_dns_interference_warned: set[str] = set()
_dns_interference_active = False
def _build_detection_doh_resolver() -> DoHResolver | None:
"""Build a throwaway DoH resolver for interference checks (no socket patching).
Honours the DoH provider the user selected (``DNS_PROVIDERS[_current_dns_index]``),
falling back to the first configured provider when none is active. The endpoint is
pinned to the provider's own nameserver IP so resolving the DoH host can't be
redirected by the very DNS layer the check is meant to detect.
"""
if 0 <= _current_dns_index < len(DNS_PROVIDERS):
_name, servers, doh_url = DNS_PROVIDERS[_current_dns_index]
elif DNS_PROVIDERS:
_name, servers, doh_url = DNS_PROVIDERS[0]
else:
return None
server_hostname = urllib.parse.urlparse(doh_url).hostname or ""
if not server_hostname or not servers:
return None
return DoHResolver(doh_url, server_hostname, servers[0])
def detect_dns_interference(hostname: str) -> dict[str, list[str]] | None:
"""Detect network DNS interference by comparing system DNS against DoH.
Returns ``{"system_ips": [...], "doh_ips": [...]}`` when the two resolvers disagree
(no overlapping IPs), otherwise None. No-op for IP literals / local hostnames and
when DoH resolution is unavailable, so it never produces a false positive.
"""
host = (hostname or "").strip().lower()
if not host or _is_ip_address(host) or _is_local_address(host):
return None
resolver = _build_detection_doh_resolver()
if resolver is None:
return None
try:
system_ips = {str(info[4][0]) for info in original_getaddrinfo(host, 443, socket.AF_INET)}
except OSError:
return None
if not system_ips:
return None
doh_ips = {ip for ip in resolver.resolve(host, "A") if ip}
if not doh_ips or (system_ips & doh_ips):
return None
return {"system_ips": sorted(system_ips), "doh_ips": sorted(doh_ips)}
def note_possible_dns_interference(hostname: str) -> bool:
"""Check ``hostname`` for DNS interference, logging an actionable warning once.
Returns True when interference has been detected this session. The check runs at
most once per host to avoid repeated DoH lookups and log spam.
"""
global _dns_interference_active
host = (hostname or "").strip().lower()
if not host or host in _dns_interference_warned:
return _dns_interference_active
_dns_interference_warned.add(host)
result = detect_dns_interference(host)
if not result:
return _dns_interference_active
_dns_interference_active = True
routing_via_doh = _current_dns_index >= 0 and bool(DOH_SERVER)
remedy = (
"Shelfmark is routing this domain through DNS-over-HTTPS to work around it."
if routing_via_doh
else "Enable DNS-over-HTTPS (USE_DOH=true) or set a custom DNS provider to bypass it."
)
logger.warning(
"Possible ISP/network DNS interference for %s: system DNS resolves to %s but DoH "
"resolves to %s. The network appears to be blocking or redirecting this domain. %s",
host,
result["system_ips"],
result["doh_ips"],
remedy,
)
return True
def dns_interference_detected() -> bool:
"""Whether network DNS interference has been detected this session."""
return _dns_interference_active
def init_doh_resolver(doh_server: str = "") -> DoHResolver | None:
"""Initialize DNS over HTTPS resolver."""
server = doh_server or DOH_SERVER
@@ -922,14 +1178,18 @@ def rotate_dns_and_reset_aa() -> bool:
configured_url = _get_configured_aa_url()
if configured_url == "auto":
# Auto mode always resets to the first mirror to restart the cascade
_current_aa_url_index = 0
if _aa_urls:
_aa_base_url = _aa_urls[0]
# Auto mode always resets to the first mirror to restart the cascade. Skip any
# quarantined ones: a new DNS provider cannot revive a parked or seized domain.
with _dead_aa_urls_lock:
restart_urls = [url for url in _aa_urls if url not in _dead_aa_urls] or _aa_urls
if restart_urls:
_aa_base_url = restart_urls[0]
_current_aa_url_index = _aa_urls.index(_aa_base_url)
logger.info("After DNS switch, resetting AA URL to: %s", _aa_base_url)
_save_state(aa_url=_aa_base_url)
else:
_aa_base_url = ""
_current_aa_url_index = 0
logger.info("After DNS switch, AA URL remains unconfigured")
else:
# Keep the user's configured primary mirror (if it exists in the list),
@@ -1099,8 +1359,17 @@ def _initialize_aa_state() -> None:
global _aa_base_url, _current_aa_url_index, _aa_urls
# Build URL list from config
previous_urls = _aa_urls
_aa_urls = _build_aa_urls()
# Drop quarantine decisions only when the mirror list itself changed - they were
# made about a list that no longer applies. This runs on every re-init (settings
# sync, DNS rotation, helper subprocess startup), and clearing unconditionally
# would resurrect a parked mirror mid-session.
if previous_urls != _aa_urls:
with _dead_aa_urls_lock:
_dead_aa_urls.clear()
# Get configured base URL from config
configured_url = _get_configured_aa_url()
@@ -1116,26 +1385,34 @@ def _initialize_aa_state() -> None:
return
if configured_url == "auto":
if state.get("aa_base_url") and state["aa_base_url"] in _aa_urls:
_current_aa_url_index = _aa_urls.index(state["aa_base_url"])
_aa_base_url = state["aa_base_url"]
# Never restore or probe a mirror quarantined this session: re-init happens
# often, and re-electing a parked domain costs a wasted request every time
# (its parking page answers 200, so the probe would happily pick it).
with _dead_aa_urls_lock:
candidates = [url for url in _aa_urls if url not in _dead_aa_urls]
restored = state.get("aa_base_url")
if restored and restored in candidates:
_current_aa_url_index = _aa_urls.index(restored)
_aa_base_url = restored
else:
logger.debug("AA_BASE_URL: auto, checking available urls %s", _aa_urls)
for i, url in enumerate(_aa_urls):
logger.debug("AA_BASE_URL: auto, checking available urls %s", candidates)
for url in candidates:
try:
response = requests.get(
url, proxies=get_proxies(url), timeout=3, verify=get_ssl_verify(url)
)
if response.status_code == HTTPStatus.OK:
_current_aa_url_index = i
_current_aa_url_index = _aa_urls.index(url)
_aa_base_url = url
_save_state(aa_url=_aa_base_url)
break
except (OSError, requests.RequestException) as exc:
logger.debug("Could not reach AA mirror candidate %s: %s", url, exc)
if not _aa_base_url or _aa_base_url == "auto":
_aa_base_url = _aa_urls[0]
_current_aa_url_index = 0
# Also covers the case where every probe failed and the previous base is
# itself quarantined - keeping it would aim the next search at a dead host.
if not _aa_base_url or _aa_base_url == "auto" or _aa_base_url not in candidates:
_aa_base_url = (candidates or _aa_urls)[0]
_current_aa_url_index = _aa_urls.index(_aa_base_url)
elif configured_url not in _aa_urls:
logger.info("AA_BASE_URL set to custom value %s; skipping auto-switch", configured_url)
_aa_base_url = configured_url
@@ -1233,24 +1510,82 @@ def is_aa_auto_mode() -> bool:
def get_available_aa_urls() -> list[str]:
"""Get list of configured AA URLs (copy)."""
"""Get configured AA URLs (copy), minus any quarantined this process.
Falls back to the full list when every mirror has been quarantined: a wrong
classification must not leave the app with nowhere to search.
"""
_ensure_initialized()
return _aa_urls.copy()
with _dead_aa_urls_lock:
alive = [url for url in _aa_urls if url not in _dead_aa_urls]
if not alive and _aa_urls:
logger.warning("All AA mirrors quarantined; retrying the full list")
_dead_aa_urls.clear()
alive = _aa_urls.copy()
# Prefer mirrors that are not serving a 429 cooldown so rotation stops hammering a
# throttled host. When every live mirror is cooling, keep the full live list rather
# than returning nothing: selection must never be left with nowhere to point, and
# the bypasser's fail-fast reports the "all rate-limited" case with a clear error.
breathing = [url for url in alive if not is_host_cooling_down(url)]
return breathing or alive
def set_aa_url_index(new_index: int) -> bool:
"""Set AA base URL by index in available list; returns True if applied."""
def _aa_base_for_url(url: str) -> str:
"""Return the configured mirror base that ``url`` belongs to, if any."""
for base in _aa_urls:
if base and url.startswith(base):
return base
return ""
def mark_aa_url_dead(url: str, reason: str) -> bool:
"""Quarantine an AA mirror for the rest of this process.
Only for hard evidence that the host is not a working AA mirror. Transient
failures (403 challenge, 429, 5xx, timeouts) must never come through here -
quarantining a live mirror throws away its bypass clearance.
"""
_ensure_initialized()
base = _aa_base_for_url(url) or url
with _dead_aa_urls_lock:
if base not in _aa_urls or base in _dead_aa_urls:
return False
# Keep at least one mirror in play, even if it is the failing one.
if len([u for u in _aa_urls if u not in _dead_aa_urls]) <= 1:
logger.warning("Not quarantining last remaining AA mirror %s (%s)", base, reason)
return False
_dead_aa_urls.add(base)
logger.warning("Quarantined AA mirror %s for this session: %s", base, reason)
return True
def get_dead_aa_urls() -> set[str]:
"""Return the mirrors quarantined this process (copy)."""
with _dead_aa_urls_lock:
return set(_dead_aa_urls)
def set_aa_url(url: str) -> bool:
"""Set the active AA base URL; returns True if applied."""
_ensure_initialized()
global _aa_base_url, _current_aa_url_index
if new_index < 0 or new_index >= len(_aa_urls):
if url not in _aa_urls:
return False
_current_aa_url_index = new_index
_aa_base_url = _aa_urls[_current_aa_url_index]
_current_aa_url_index = _aa_urls.index(url)
_aa_base_url = url
logger.info("Set AA URL to: %s", _aa_base_url)
_save_state(aa_url=_aa_base_url)
return True
def set_aa_url_index(new_index: int) -> bool:
"""Set AA base URL by index in the full configured list; True if applied."""
_ensure_initialized()
if new_index < 0 or new_index >= len(_aa_urls):
return False
return set_aa_url(_aa_urls[new_index])
class AAMirrorSelector:
"""Keep AA mirror switching consistent across call sites.
@@ -1259,11 +1594,20 @@ class AAMirrorSelector:
def __init__(self) -> None:
"""Initialize mirror state from the current AA configuration."""
# Set by html_get_page at each give-up path so a caller that only sees the
# returned empty page can still report *why* the fetch produced nothing
# (403, 404, redirect loop, bypasser error, mirrors exhausted, ...) instead
# of a blanket "network restricted" guess. None means "no failure recorded".
self.last_failure: str | None = None
self._ensure_fresh_state(reset_attempts=True)
def _ensure_fresh_state(self, *, reset_attempts: bool = False) -> None:
_ensure_initialized()
self.aa_urls = get_available_aa_urls()
# Rotation walks the live mirrors, but rewriting has to recognise every
# configured base: a URL built before a mirror was quarantined still points at
# it, and failing to rewrite would send the retry back to the dead host.
self.all_aa_urls = _aa_urls.copy()
self._index = self._safe_index(get_aa_base_url())
self.current_base = self.aa_urls[self._index] if self.aa_urls else ""
if reset_attempts:
@@ -1276,16 +1620,41 @@ class AAMirrorSelector:
def rewrite(self, url: str) -> str:
"""Replace any known AA base in url with current_base."""
for base in self.aa_urls:
for base in self.all_aa_urls:
if url.startswith(base):
return url.replace(base, self.current_base, 1)
return url
def next_mirror_or_rotate_dns(self, *, allow_dns: bool = True) -> tuple[str | None, str]:
def quarantine_current(self, reason: str) -> bool:
"""Quarantine the mirror this selector is on (hard failures only)."""
if not self.current_base:
return False
dropped = mark_aa_url_dead(self.current_base, reason)
if dropped:
# Rebuild from the surviving mirrors so the dead one is out of the cycle.
self._ensure_fresh_state(reset_attempts=False)
return dropped
def next_mirror_or_rotate_dns(
self, *, allow_dns: bool = True, fatal: bool = False, reason: str = ""
) -> tuple[str | None, str]:
"""Advance to the next mirror or rotate DNS if needed.
``fatal`` marks the current mirror as not-an-AA-mirror (NXDOMAIN, refused, a
200 that isn't AA) and drops it from this process's rotation. Leave it False
for anything the mirror can recover from - a challenge or a 5xx means the host
is alive, and quarantining it would discard its bypass clearance.
Returns (new_base, action) where action is 'mirror', 'dns', or 'exhausted'.
"""
if fatal and self.quarantine_current(reason or "unusable mirror"):
# Quarantining rebuilt the state onto a surviving mirror, so that mirror is
# the next one to try - advancing again here would skip straight past it.
self.attempts_this_dns += 1
if self.current_base and is_aa_auto_mode():
set_aa_url(self.current_base)
return self.current_base, "mirror"
self.attempts_this_dns += 1
max_attempts = len(self.aa_urls) if is_aa_auto_mode() else 1
if self.attempts_this_dns >= max_attempts:
@@ -1298,8 +1667,11 @@ class AAMirrorSelector:
# Mirror is explicitly configured; do not fail over to other mirrors.
return None, "exhausted"
if not self.aa_urls:
return None, "exhausted"
next_index = (self._index + 1) % len(self.aa_urls)
set_aa_url_index(next_index)
set_aa_url(self.aa_urls[next_index])
self._ensure_fresh_state(reset_attempts=False)
return self.current_base, "mirror"
+189 -24
View File
@@ -12,7 +12,7 @@ from concurrent.futures import Future, ThreadPoolExecutor
from email.utils import parseaddr
from pathlib import Path
from threading import Event, Lock
from typing import Any
from typing import TYPE_CHECKING, Any
from shelfmark.core.config import config
from shelfmark.core.logger import setup_logger
@@ -24,15 +24,20 @@ from shelfmark.core.request_helpers import (
)
from shelfmark.core.utils import is_audiobook as check_audiobook
from shelfmark.core.utils import transform_cover_url
from shelfmark.download.activity import parse_activity_grace
from shelfmark.download.fs import run_blocking_io
from shelfmark.download.postprocess.pipeline import is_torrent_source, safe_cleanup_path
from shelfmark.download.postprocess.router import post_process_download
from shelfmark.release_sources import (
HandoffResult,
get_handler,
get_source,
get_source_display_name,
)
if TYPE_CHECKING:
from collections.abc import Iterable
logger = setup_logger(__name__)
_RNG = random.SystemRandom()
@@ -65,7 +70,16 @@ _last_progress_value: dict[str, float] = {}
# De-duplicate status updates (keep-alive updates shouldn't spam clients)
_last_status_event: dict[str, tuple[str, str | None]] = {}
STALL_TIMEOUT = 300 # 5 minutes without progress/status update = stalled
# Absolute deadlines (time.time()) until which stall detection is suppressed for a task.
# Long single-shot operations (protection bypass, etc.) declare their own upper bound via
# `shelfmark.download.activity` instead of faking progress. See set_activity_grace().
_activity_grace: dict[str, float] = {}
# A caller cannot buy immortality: the largest grace any operation may request. Must stay
# above the largest budget any caller can declare (see http._bypass_grace_seconds).
_MAX_ACTIVITY_GRACE_SECONDS = 960.0
COORDINATOR_LOOP_ERROR_RETRY_DELAY = 1.0
# Ceiling for the exponential backoff applied to repeated coordinator loop failures.
_COORDINATOR_LOOP_ERROR_MAX_DELAY = 30.0
_PROGRESS_BROADCAST_START_PERCENT = 1
_PROGRESS_BROADCAST_COMPLETE_PERCENT = 99
_PROGRESS_BROADCAST_MIN_DELTA = 10
@@ -170,16 +184,19 @@ def _build_retry_resolution_fields(
retry_download_url = normalize_optional_text(release_data.get("download_url"))
protocol = normalize_optional_text(release_data.get("protocol"))
source = normalize_optional_text(release_data.get("source"))
retry_source_context: dict[str, Any] = {}
if source is not None:
handler = get_handler(source)
source_retry_fields = handler.build_retry_resolution_fields(release_data)
retry_download_url = (
normalize_optional_text(source_retry_fields.get("retry_download_url"))
or retry_download_url
)
protocol = (
normalize_optional_text(source_retry_fields.get("retry_download_protocol")) or protocol
)
if "retry_download_url" in source_retry_fields:
retry_download_url = normalize_optional_text(
source_retry_fields.get("retry_download_url")
)
if "retry_download_protocol" in source_retry_fields:
protocol = normalize_optional_text(source_retry_fields.get("retry_download_protocol"))
raw_retry_source_context = source_retry_fields.get("retry_source_context")
if isinstance(raw_retry_source_context, dict):
retry_source_context = dict(raw_retry_source_context)
ratio_limit = _optional_number(release_data.get("ratio_limit"))
if ratio_limit is None and config.get("PROWLARR_USE_SEED_PREFERENCES", False):
@@ -202,6 +219,7 @@ def _build_retry_resolution_fields(
),
"retry_ratio_limit": ratio_limit,
"retry_seeding_time_limit_minutes": seeding_time_limit_minutes,
"retry_source_context": retry_source_context,
"can_retry_without_staged_source": True,
}
@@ -247,6 +265,9 @@ def queue_release(
series_name = release_data.get("series_name") or extra.get("series_name")
series_position = release_data.get("series_position") or extra.get("series_position")
subtitle = release_data.get("subtitle") or extra.get("subtitle")
language = release_data.get("language") or extra.get("language")
multi_book = bool(release_data.get("multi_book") or extra.get("multi_book"))
book_plan = _normalize_book_plan(release_data.get("book_plan") or extra.get("book_plan"))
books_output_mode = (
str(config.get("BOOKS_OUTPUT_MODE", "folder", user_id=user_id) or "folder")
@@ -275,12 +296,16 @@ def queue_release(
year=year,
format=release_data.get("format"),
size=release_data.get("size"),
downloads=release_data.get("downloads") or extra.get("downloads"),
preview=preview,
content_type=content_type,
source_url=source_url,
series_name=series_name,
series_position=series_position,
subtitle=subtitle,
language=language,
multi_book=multi_book or book_plan is not None,
book_plan=book_plan,
search_mode=search_mode,
output_mode=output_mode,
output_args=output_args,
@@ -295,7 +320,14 @@ def queue_release(
logger.info("Release already in queue: %s", task.title)
return False, "Release is already in the download queue"
logger.info("Release queued with priority %s: %s", priority, task.title)
logger.info(
"Release queued with priority %s: %s (downloads=%s, release_data.downloads=%s, extra=%s)",
priority,
task.title,
task.downloads,
release_data.get("downloads"),
extra,
)
# Broadcast status update via WebSocket
if ws_manager:
@@ -389,6 +421,33 @@ def can_retry_download_task(
return _has_staged_retry_source(task)
def _normalize_book_plan(value: object) -> list[dict[str, Any]] | None:
"""Keep only well-formed pack books: a title plus a non-empty list of file paths."""
if not isinstance(value, list):
return None
books: list[dict[str, Any]] = []
for entry in value:
if not isinstance(entry, dict):
continue
title = normalize_optional_text(entry.get("title"))
raw_files = entry.get("files")
if title is None or not isinstance(raw_files, list):
continue
files = [f for f in raw_files if isinstance(f, str) and f.strip()]
if not files:
continue
year = entry.get("year")
books.append(
{
"title": title,
"series_position": _optional_number(entry.get("series_position")),
"year": year if isinstance(year, int) and not isinstance(year, bool) else None,
"files": files,
}
)
return books or None
def serialize_task_for_retry(task: DownloadTask) -> dict[str, Any]:
"""Serialize the task state needed for restart-safe retries."""
raw_search_mode = getattr(task, "search_mode", None)
@@ -400,6 +459,7 @@ def serialize_task_for_retry(task: DownloadTask) -> dict[str, Any]:
search_mode = normalized_search_mode or None
raw_output_args = getattr(task, "output_args", None)
raw_retry_source_context = getattr(task, "retry_source_context", None)
return {
"task_id": getattr(task, "task_id", None),
@@ -415,7 +475,10 @@ def serialize_task_for_retry(task: DownloadTask) -> dict[str, Any]:
"series_name": getattr(task, "series_name", None),
"series_position": getattr(task, "series_position", None),
"subtitle": getattr(task, "subtitle", None),
"language": getattr(task, "language", None),
"search_mode": search_mode,
"multi_book": bool(getattr(task, "multi_book", False)),
"book_plan": _normalize_book_plan(getattr(task, "book_plan", None)),
"output_mode": getattr(task, "output_mode", None),
"output_args": dict(raw_output_args) if isinstance(raw_output_args, dict) else {},
"user_id": getattr(task, "user_id", None),
@@ -428,6 +491,9 @@ def serialize_task_for_retry(task: DownloadTask) -> dict[str, Any]:
"retry_expected_hash": getattr(task, "retry_expected_hash", None),
"retry_ratio_limit": getattr(task, "retry_ratio_limit", None),
"retry_seeding_time_limit_minutes": getattr(task, "retry_seeding_time_limit_minutes", None),
"retry_source_context": (
dict(raw_retry_source_context) if isinstance(raw_retry_source_context, dict) else {}
),
"can_retry_without_staged_source": bool(
getattr(task, "can_retry_without_staged_source", True)
),
@@ -453,6 +519,7 @@ def _restore_task_from_retry_payload(payload: object) -> DownloadTask | None:
search_mode = None
output_args = payload.get("output_args")
retry_source_context = payload.get("retry_source_context")
return DownloadTask(
task_id=task_id,
@@ -462,13 +529,17 @@ def _restore_task_from_retry_payload(payload: object) -> DownloadTask | None:
year=normalize_optional_text(payload.get("year")),
format=normalize_optional_text(payload.get("format")),
size=normalize_optional_text(payload.get("size")),
downloads=int(payload["downloads"]) if payload.get("downloads") is not None else None,
preview=normalize_optional_text(payload.get("preview")),
content_type=normalize_optional_text(payload.get("content_type")),
source_url=normalize_optional_text(payload.get("source_url")),
series_name=normalize_optional_text(payload.get("series_name")),
series_position=_optional_number(payload.get("series_position")),
subtitle=normalize_optional_text(payload.get("subtitle")),
language=normalize_optional_text(payload.get("language")),
search_mode=search_mode,
multi_book=bool(payload.get("multi_book", False)),
book_plan=_normalize_book_plan(payload.get("book_plan")),
output_mode=normalize_optional_text(payload.get("output_mode")),
output_args=dict(output_args) if isinstance(output_args, dict) else {},
user_id=normalize_positive_int(payload.get("user_id")),
@@ -483,6 +554,9 @@ def _restore_task_from_retry_payload(payload: object) -> DownloadTask | None:
retry_seeding_time_limit_minutes=_optional_positive_int(
payload.get("retry_seeding_time_limit_minutes")
),
retry_source_context=(
dict(retry_source_context) if isinstance(retry_source_context, dict) else {}
),
can_retry_without_staged_source=bool(payload.get("can_retry_without_staged_source", True)),
)
@@ -552,6 +626,7 @@ def _task_to_dict(
"author": task.author,
"format": task.format,
"size": task.size,
"downloads": task.downloads,
"preview": preview,
"content_type": task.content_type,
"source": task.source,
@@ -633,6 +708,17 @@ def _download_task(task_id: str, cancel_flag: Event) -> str | None:
update_download_progress(task_id, progress)
def status_callback(status: str, message: str | None = None) -> None:
# Liveness hint from a long single-shot operation, not a user-visible status.
# Handled here so it never reaches update_download_status (which dedupes status
# transitions on purpose). See shelfmark.download.activity.
grace = parse_activity_grace(status, message)
if grace is not None:
if grace > 0:
set_activity_grace(task_id, grace)
else:
clear_activity_grace(task_id)
return
status_key = status.lower()
if status_key == "error":
_capture_task_error(
@@ -675,6 +761,13 @@ def _download_task(task_id: str, cancel_flag: Event) -> str | None:
if not temp_path:
return None
if isinstance(temp_path, HandoffResult):
handoff_path = Path(temp_path.path)
status_callback("complete", temp_path.message)
handler.post_process_cleanup(task, success=True)
_clear_task_error_state(task)
return str(handoff_path)
temp_file = Path(temp_path)
if not run_blocking_io(temp_file.exists):
logger.error("Handler returned non-existent path: %s", temp_path)
@@ -870,6 +963,7 @@ def _cleanup_progress_tracking(task_id: str) -> None:
_last_activity.pop(task_id, None)
_last_progress_value.pop(task_id, None)
_last_status_event.pop(task_id, None)
_activity_grace.pop(task_id, None)
def _finalize_download_failure(task_id: str) -> None:
@@ -917,6 +1011,66 @@ def _process_single_download(task_id: str, cancel_flag: Event) -> None:
ws_manager.broadcast_status_update(queue_status())
def set_activity_grace(book_id: str, seconds: float) -> None:
"""Suppress stall detection for `book_id` for up to `seconds` from now.
For long single-shot operations that cannot report incremental progress (protection
bypass being the motivating case). The grace is a single absolute deadline computed
once, so it cannot be extended into immortality by a keep-alive that carries no real
liveness information - an operation that hangs forever is still cancelled once its
declared budget expires.
Deliberately touches neither the queue nor the WebSocket: this is a liveness
assertion, not a user-visible status transition.
"""
grace = _config_float(seconds, 0.0)
grace = min(max(grace, 0.0), _MAX_ACTIVITY_GRACE_SECONDS)
with _progress_lock:
_activity_grace[book_id] = time.time() + grace
def clear_activity_grace(book_id: str) -> None:
"""Drop any activity grace for `book_id` and count this moment as activity.
Resetting `_last_activity` means a nested or abandoned grace degrades to a fresh
full STALL_TIMEOUT window rather than an immediate stall.
"""
with _progress_lock:
_activity_grace.pop(book_id, None)
_last_activity[book_id] = time.time()
def _find_stalled_tasks(task_ids: Iterable[str], now: float) -> list[str]:
"""Return the task ids with no activity inside STALL_TIMEOUT and no active grace.
Holds `_progress_lock` for dict reads only - never call into `book_queue` from here,
see _cancel_stalled_task().
"""
stalled: list[str] = []
with _progress_lock:
for task_id in task_ids:
last_active = _last_activity.get(task_id, now)
deadline = max(last_active + STALL_TIMEOUT, _activity_grace.get(task_id, 0.0))
if now > deadline:
stalled.append(task_id)
return stalled
def _cancel_stalled_task(task_id: str) -> None:
"""Cancel a stalled download.
Must be called WITHOUT `_progress_lock` held. `book_queue.cancel_download` runs the
terminal-status hooks, which reach a sqlite write that gevent does not patch; holding
the progress lock across that blocks the hub and every other download worker.
"""
logger.warning("Download stalled for %s, cancelling", task_id)
book_queue.cancel_download(task_id)
book_queue.update_status_message(
task_id,
f"Download stalled (no activity for {STALL_TIMEOUT}s)",
)
def concurrent_download_loop() -> None:
"""Run the main concurrent download coordinator."""
max_workers = normalize_positive_int(config.MAX_CONCURRENT_DOWNLOADS) or 1
@@ -926,6 +1080,7 @@ def concurrent_download_loop() -> None:
with ThreadPoolExecutor(max_workers=max_workers, thread_name_prefix="Download") as executor:
active_futures: dict[Future, tuple[str, Event]] = {} # Track active download futures
stalled_tasks: set[str] = set() # Track tasks already cancelled due to stall
consecutive_errors = 0
while True:
try:
@@ -976,19 +1131,14 @@ def concurrent_download_loop() -> None:
# Check for stalled downloads (no activity in STALL_TIMEOUT seconds)
current_time = time.time()
with _progress_lock:
for _future, (task_id, _cancel_flag) in list(active_futures.items()):
if task_id in stalled_tasks:
continue
last_active = _last_activity.get(task_id, current_time)
if current_time - last_active > STALL_TIMEOUT:
logger.warning("Download stalled for %s, cancelling", task_id)
book_queue.cancel_download(task_id)
book_queue.update_status_message(
task_id,
f"Download stalled (no activity for {STALL_TIMEOUT}s)",
)
stalled_tasks.add(task_id)
candidates = [
task_id
for _future, (task_id, _cancel_flag) in list(active_futures.items())
if task_id not in stalled_tasks
]
for task_id in _find_stalled_tasks(candidates, current_time):
_cancel_stalled_task(task_id)
stalled_tasks.add(task_id)
# Start new downloads if we have capacity
while len(active_futures) < max_workers:
@@ -1011,9 +1161,24 @@ def concurrent_download_loop() -> None:
# Brief sleep to prevent busy waiting
time.sleep(main_loop_sleep_time)
except (AttributeError, KeyError, OSError, RuntimeError, TypeError, ValueError) as e:
consecutive_errors = 0
# This loop is the only thing driving the download queue; if it exits, nothing
# is ever picked up again and the app looks healthy while doing nothing (#823,
# #1166). A narrow exception list let gevent's LoopExit and friends through, so
# catch everything short of BaseException - GreenletExit and gevent.Timeout must
# still propagate, and the tests' loop-stopping sentinels derive from
# BaseException for exactly this reason.
except Exception as e: # noqa: BLE001 - coordinator loop must never die
consecutive_errors += 1
logger.error_trace("Download coordinator loop error: %s", e)
time.sleep(COORDINATOR_LOOP_ERROR_RETRY_DELAY)
# Back off when the failure is persistent so we don't spin at 1Hz forever,
# but keep the first delay unchanged for a normal transient blip.
time.sleep(
min(
COORDINATOR_LOOP_ERROR_RETRY_DELAY * 2 ** min(consecutive_errors - 1, 5),
_COORDINATOR_LOOP_ERROR_MAX_DELAY,
)
)
# Download coordinator thread (started explicitly via start())
+11 -1
View File
@@ -105,6 +105,7 @@ def process_folder_output(
maybe_run_custom_script,
prepare_output_files,
record_step,
resolve_book_groups,
transfer_book_files,
)
@@ -205,6 +206,7 @@ def process_folder_output(
is_torrent=is_torrent,
preserve_source=preserve_source,
organization_mode=plan.organization_mode,
source_root=source_path,
)
if error:
@@ -259,7 +261,15 @@ def process_folder_output(
prepared.cleanup_paths,
)
message = "Complete" if len(final_paths) == 1 else f"Complete ({len(final_paths)} files)"
pack_groups = resolve_book_groups(
task, prepared.files, organization_mode=plan.organization_mode
)
if pack_groups is not None:
message = f"Complete ({len(pack_groups)} books, {len(final_paths)} files)"
elif len(final_paths) == 1:
message = "Complete"
else:
message = f"Complete ({len(final_paths)} files)"
status_callback("complete", message)
return str(final_paths[0])
@@ -229,6 +229,7 @@ def _build_custom_script_payload(
"series_name": context.task.series_name,
"series_position": context.task.series_position,
"subtitle": context.task.subtitle,
"language": context.task.language,
"original_download_path": context.task.original_download_path,
},
"output": {
+27 -4
View File
@@ -3,7 +3,6 @@
from __future__ import annotations
import contextlib
import uuid
from typing import TYPE_CHECKING
from shelfmark.core.logger import setup_logger
@@ -13,7 +12,11 @@ from shelfmark.core.utils import (
from shelfmark.core.utils import (
is_audiobook as check_audiobook,
)
from shelfmark.download.fs import run_blocking_io
from shelfmark.download.fs import (
clear_delete_denied,
mark_delete_denied,
run_blocking_io,
)
from shelfmark.download.permissions_debug import log_path_permission_context
from shelfmark.release_sources import get_source
@@ -25,6 +28,8 @@ if TYPE_CHECKING:
logger = setup_logger("shelfmark.download.postprocess.pipeline")
_WRITE_PROBE_NAME = ".shelfmark_write_test.tmp"
def validate_destination(
destination: Path, status_callback: Callable[[str, str | None], None]
@@ -52,7 +57,9 @@ def validate_destination(
status_callback("error", f"Cannot create destination: {destination} ({exc})")
return False
test_path = destination / f".shelfmark_write_test_{uuid.uuid4().hex}.tmp"
# Stable name: on shares that refuse deletes the probe file cannot be cleaned
# up, so reusing one name bounds the leftovers at a single hidden file.
test_path = destination / _WRITE_PROBE_NAME
try:
test_content = (
@@ -60,7 +67,6 @@ def validate_destination(
"It should've been automatically deleted. Feel free to delete it.\n"
)
run_blocking_io(test_path.write_text, test_content)
run_blocking_io(test_path.unlink, missing_ok=True)
except OSError as exc:
logger.debug("Destination write probe path: %s", test_path)
log_path_permission_context("destination_write_probe", destination)
@@ -71,6 +77,23 @@ def validate_destination(
run_blocking_io(destination.rmdir)
return False
try:
run_blocking_io(test_path.unlink, missing_ok=True)
except OSError as exc:
# Writable but not deletable, e.g. a Synology share with "Delete
# subfolders and files" unticked. Not fatal: record it so transfers write
# files in place instead of publishing a temp file via rename.
mark_delete_denied(destination)
logger.warning(
"Destination %s is writable but refuses deletes (%s); leaving probe file %s "
"behind and writing files in place",
destination,
exc,
test_path.name,
)
else:
clear_delete_denied(destination)
return True
+442
View File
@@ -0,0 +1,442 @@
"""Multi-book ("pack") release planning.
A pack is one release that contains several books: a whole-series torrent with one
subfolder per book, or a flat folder of `Series 1.0 - Title.m4b` files. The same
planning rules serve pre-download inspection (the file list comes from the release
source) and post-processing (the file list comes from disk), so what the user
approved in the modal is what gets filed.
"""
from __future__ import annotations
import os
import re
from dataclasses import dataclass
from pathlib import Path, PurePosixPath
from shelfmark.core.utils import AUDIOBOOK_FORMATS
# m4b/m4a hold a whole audiobook in one file; every other audio format (mp3, flac, ...) is
# chaptered - many files make up one book. Ebook formats are always one file per book, so
# only chaptered *audio* matters here. A flat folder is split one-book-per-file only when
# none of its files are chaptered audio: a bare list of `01 - Chapter.mp3` tracks is a
# single chaptered audiobook, not a pack of books.
_SINGLE_FILE_AUDIO_CONTAINERS = frozenset({"m4b", "m4a"})
_CHAPTERED_AUDIO_EXTENSIONS = frozenset(AUDIOBOOK_FORMATS) - _SINGLE_FILE_AUDIO_CONTAINERS
_YEAR_SUFFIX_RE = re.compile(r"\s*\(\s*(?P<year>\d{4})\s*\)\s*$")
_SERIES_MARKER_RE = re.compile(
r"""
^\s*
(?:
\[\s*\#?(?P<bracket>\d+(?:\.\d+)?)\s*\] # [03] / [#3]
| \#(?P<hash>\d+(?:\.\d+)?) # #3
| book\.?\s*(?P<book>\d+(?:\.\d+)?) # Book 3 / Book. 03
| (?P<plain>\d+(?:\.\d+)?)(?=[\s\-:.]) # 03 - / 1.0 - / 3.
)
\s*(?:[-:.]\s*)?
""",
re.IGNORECASE | re.VERBOSE,
)
_SEPARATOR_CHARS = " \t-_:."
# "Gods of Risk 2.5 - Gods of Risk": the title repeated on both sides of the position.
_REPEATED_TITLE_RE = re.compile(
r"^(?P<left>.+?)\s+(?P<position>\d+(?:\.\d+)?)\s*[-:\u2013]\s*(?P<right>.+)$"
)
_SERIES_LABEL_WORDS = r"(?:novella|novellas|short\s+story|short|story|novel)"
# "Uncrowned Cradle, Book 7" / "Reaper Cradle, Volume 10" / "Wintersteel (Cradle, Book 8)":
# an explicit word marks the position at the END of the name. A bare trailing number
# is deliberately not matched — "Title - 02" is a chapter, not a series position.
_TRAILING_MARKER_RE = re.compile(
r"""
[\s,\-:\u2013(]*
(?:book|volume|vol\.?)\s*\#?(?P<position>\d+(?:\.\d+)?)
\s*\)?\s*$
""",
re.IGNORECASE | re.VERBOSE,
)
# AudiobookBay renders a file inside a folder as "<folder> <file>" with no separator,
# so a pack row reads "Author - Title Series, Book 1 Title Series, Book 1".
_GLUED_FOLDER_RE = re.compile(
r"^(?P<prefix>.+?\s[-\u2013]\s)?(?P<core>.+?)\s+(?P=core)$", re.IGNORECASE
)
@dataclass(frozen=True)
class PackFile:
"""One file inside a release, path relative to the release root."""
path: str
size: int | None = None
@dataclass(frozen=True)
class PackBook:
"""One book split out of a pack, files as release-relative paths."""
title: str
series_position: float | None
year: int | None
files: list[str]
@dataclass(frozen=True)
class PackPlan:
books: list[PackBook]
ignored: list[str]
@property
def is_pack(self) -> bool:
return len(self.books) > 1
@dataclass(frozen=True)
class BookGroup:
"""One book's on-disk files, ready for transfer."""
title: str
series_position: float | None
year: int | None
files: list[Path]
def _strip_series_name(name: str, series_name: str | None) -> str:
if not series_name:
return name
prefix = series_name.strip()
if not prefix or not name.lower().startswith(prefix.lower()):
return name
remainder = name[len(prefix) :]
if remainder and remainder[0].isalnum():
return name
return remainder.lstrip(_SEPARATOR_CHARS)
def _strip_series_label(work: str, series_name: str | None) -> str:
"""Drop a leading "An <Series> Novella - " style label that some packs prepend."""
if not series_name:
return work
# "The Expanse" is labelled "An Expanse Novella", so match without the article.
core = re.sub(r"^(?:the|an?)\s+", "", series_name.strip(), flags=re.IGNORECASE)
if not core:
return work
pattern = re.compile(
rf"^(?:an?\s+|the\s+)?{re.escape(core)}\s+{_SERIES_LABEL_WORDS}\s*[-:\u2013]\s*",
re.IGNORECASE,
)
return pattern.sub("", work, count=1)
def _collapse_glued_folder(name: str) -> str:
match = _GLUED_FOLDER_RE.match(name)
if not match:
return name
prefix = match.group("prefix") or ""
core = match.group("core")
# "Author - X X" → "Author - X" (the folder carried the author, the file did not).
return (prefix + core).strip()
def _strip_author_name(name: str, author_name: str | None) -> str:
"""Drop a leading "Author - " (packs are often filed as `Author - Title`)."""
if not author_name:
return name
prefix = author_name.strip()
if not prefix or not name.lower().startswith(prefix.lower()):
return name
remainder = name[len(prefix) :]
stripped = remainder.lstrip(_SEPARATOR_CHARS + "\u2013")
if stripped == remainder: # no separator after the author: part of the title
return name
return stripped
def _strip_trailing_series_name(work: str, series_name: str | None) -> str:
"""Drop a trailing series name left behind by a trailing position marker."""
if not series_name:
return work
suffix = series_name.strip()
if not suffix or not work.lower().endswith(suffix.lower()):
return work
remainder = work[: -len(suffix)]
stripped = remainder.rstrip(_SEPARATOR_CHARS + ",(\u2013")
if not stripped or stripped == remainder:
return work
return stripped
def parse_pack_book_name(
name: str, *, series_name: str | None, author_name: str | None = None
) -> tuple[str, float | None, int | None]:
"""Split a book folder/file-stem name into (title, series position, year).
Strips a leading series name, a leading position marker (`Book 3 - `, `03 - `,
`1.0 - `, `3. `, `[03] `, `#3 `) and a trailing `(YYYY)`. Also understands a
trailing marker (`Title Series, Book 3`, `Title (Series, Volume 3)`), a leading
`Author - `, and AudiobookBay's glued `<folder> <file>` names. Returns the name
unchanged with no position/year when nothing would be left of the title.
"""
work = _collapse_glued_folder(name.strip())
work = _strip_author_name(work, author_name)
work = _strip_series_name(work, series_name)
year: int | None = None
year_match = _YEAR_SUFFIX_RE.search(work)
if year_match:
year = int(year_match.group("year"))
work = work[: year_match.start()]
position: float | None = None
repeated = _REPEATED_TITLE_RE.match(work.strip())
if (
repeated
and repeated.group("left").strip().lower() == repeated.group("right").strip().lower()
):
return repeated.group("right").strip(), float(repeated.group("position")), year
marker = _SERIES_MARKER_RE.match(work)
if marker:
raw = (
marker.group("bracket")
or marker.group("hash")
or marker.group("book")
or marker.group("plain")
)
position = float(raw)
work = work[marker.end() :]
else:
trailing = _TRAILING_MARKER_RE.search(work)
if trailing and trailing.start() > 0:
position = float(trailing.group("position"))
work = _strip_trailing_series_name(work[: trailing.start()], series_name)
work = _strip_series_label(work, series_name)
title = work.strip().strip(_SEPARATOR_CHARS).strip()
if not title:
return name, None, None
return title, position, year
def _book_from_name(
name: str, files: list[str], series_name: str | None, author_name: str | None = None
) -> PackBook:
title, position, year = parse_pack_book_name(
name, series_name=series_name, author_name=author_name
)
return PackBook(title=title, series_position=position, year=year, files=files)
def _common_root_parts(paths: list[PurePosixPath]) -> tuple[str, ...]:
parents = [p.parent.parts for p in paths]
common: list[str] = []
for parts in zip(*parents, strict=False):
if len(set(parts)) != 1:
break
common.append(parts[0])
return tuple(common)
def plan_pack(
files: list[PackFile],
*,
supported_extensions: set[str],
series_name: str | None,
author_name: str | None = None,
root_depth: int | None = None,
) -> PackPlan:
"""Group a release's file list into books.
Files in a subfolder (relative to the common root) group by that subfolder. Files
directly in the root split one-book-per-file only when at least two of them carry
a series position in their names; otherwise they are one book (a chaptered
audiobook, e.g. `01.mp3`, `02.mp3`). `root_depth` fixes how many leading path
components form the root instead of deriving it from the files' common parent.
"""
supported = {ext.lower().lstrip(".") for ext in supported_extensions}
book_files: list[PurePosixPath] = []
ignored: list[str] = []
for pack_file in files:
rel = PurePosixPath(pack_file.path.replace("\\", "/").lstrip("./"))
if rel.suffix.lower().lstrip(".") in supported:
book_files.append(rel)
else:
ignored.append(pack_file.path)
if not book_files:
return PackPlan(books=[], ignored=ignored)
root_parts = (
_common_root_parts(book_files) if root_depth is None else book_files[0].parts[:root_depth]
)
depth = len(root_parts)
root_files: list[PurePosixPath] = []
folders: dict[str, list[str]] = {}
for rel in book_files:
remainder = rel.parts[depth:]
if len(remainder) > 1:
folders.setdefault(remainder[0], []).append(str(rel))
else:
root_files.append(rel)
books: list[PackBook] = []
if root_files:
parsed = [
parse_pack_book_name(f.stem, series_name=series_name, author_name=author_name)
for f in root_files
]
positions = {p[1] for p in parsed if p[1] is not None}
titles = {p[0].strip().lower() for p in parsed if p[0]}
one_book_per_file = all(
rel.suffix.lower().lstrip(".") not in _CHAPTERED_AUDIO_EXTENSIONS for rel in root_files
)
# Split a flat folder into a book per file only with real evidence of distinct
# books: two or more series positions, more than one title, and no chaptered audio
# (a bare list of `01 - Chapter.mp3` tracks is one book, not a pack).
if len(positions) >= 2 and len(titles) >= 2 and one_book_per_file:
books.extend(
PackBook(title=title, series_position=position, year=year, files=[str(f)])
for f, (title, position, year) in zip(root_files, parsed, strict=True)
)
elif len(root_files) == 1:
books.append(
_book_from_name(root_files[0].stem, [str(root_files[0])], series_name, author_name)
)
else:
group_name = root_parts[-1] if root_parts else ""
books.append(
_book_from_name(group_name, [str(f) for f in root_files], series_name, author_name)
)
books.extend(
_book_from_name(folder, paths, series_name, author_name)
for folder, paths in folders.items()
)
return PackPlan(books=books, ignored=ignored)
def _relative_paths(
book_files: list[Path], root: Path | None = None
) -> tuple[Path, dict[Path, str]]:
if root is None:
root = Path(os.path.commonpath([str(f.parent) for f in book_files]))
return root, {f: f.relative_to(root).as_posix() for f in book_files}
def group_files_into_books(
book_files: list[Path],
*,
series_name: str | None,
author_name: str | None = None,
root: Path | None = None,
) -> list[BookGroup]:
"""Heuristically split on-disk files into books (see `plan_pack`).
`root` pins the release root when grouping a subset of a larger file set.
"""
if not book_files:
return []
_root, rel_by_path = _relative_paths(book_files, root)
path_by_rel = {rel: path for path, rel in rel_by_path.items()}
extensions = {f.suffix.lower().lstrip(".") for f in book_files}
plan = plan_pack(
[PackFile(rel) for rel in rel_by_path.values()],
supported_extensions=extensions,
series_name=series_name,
author_name=author_name,
root_depth=None if root is None else 0,
)
return [
BookGroup(
title=book.title,
series_position=book.series_position,
year=book.year,
files=[path_by_rel[rel] for rel in book.files],
)
for book in plan.books
]
def match_plan_to_files(
plan: list[PackBook],
book_files: list[Path],
*,
series_name: str | None = None,
author_name: str | None = None,
) -> list[BookGroup]:
"""Apply an approved plan to on-disk files.
Files match by release-relative path first, then by basename (archive extraction
and client save paths can shift the root), then by the on-disk basename being a
suffix of the planned name (sources that glue folder and file names together).
Book files the plan does not mention fall back to heuristic grouping so nothing
is silently dropped.
"""
if not book_files:
return []
root, rel_by_path = _relative_paths(book_files)
by_rel = {rel: path for path, rel in rel_by_path.items()}
by_name: dict[str, list[Path]] = {}
for path in book_files:
by_name.setdefault(path.name, []).append(path)
claimed: set[Path] = set()
groups: list[BookGroup] = []
for book in plan:
matched: list[Path] = []
for wanted in book.files:
wanted_rel = wanted.replace("\\", "/").lstrip("./")
candidate = by_rel.get(wanted_rel)
if candidate is None:
candidates = [
p for p in by_name.get(PurePosixPath(wanted_rel).name, []) if p not in claimed
]
candidate = candidates[0] if candidates else None
if candidate is None:
wanted_name = PurePosixPath(wanted_rel).name.lower()
candidates = [
p
for p in book_files
if p not in claimed and wanted_name.endswith(p.name.lower())
]
candidate = candidates[0] if len(candidates) == 1 else None
if candidate is not None and candidate not in claimed:
claimed.add(candidate)
matched.append(candidate)
if matched:
groups.append(
BookGroup(
title=book.title,
series_position=book.series_position,
year=book.year,
files=matched,
)
)
unmatched = [p for p in book_files if p not in claimed]
still_unmatched: list[Path] = []
for p in unmatched:
p_ext = p.suffix.lower().lstrip(".")
is_chaptered = p_ext in _CHAPTERED_AUDIO_EXTENSIONS
matching_groups = [g for g in groups if any(f.parent == p.parent for f in g.files)]
target_group: BookGroup | None = None
if len(matching_groups) == 1 and is_chaptered:
target_group = matching_groups[0]
elif len(plan) == 1 and len(groups) == 1 and is_chaptered:
target_group = groups[0]
if target_group is not None:
target_group.files.append(p)
claimed.add(p)
else:
still_unmatched.append(p)
if still_unmatched:
groups.extend(
group_files_into_books(
still_unmatched, series_name=series_name, author_name=author_name, root=root
)
)
return groups
@@ -40,6 +40,7 @@ from .transfer import (
build_metadata_dict,
is_torrent_source,
process_directory,
resolve_book_groups,
resolve_hardlink_source,
should_hardlink,
transfer_book_files,
@@ -80,6 +81,7 @@ __all__ = [
"process_directory",
"record_step",
"resolve_custom_script_target",
"resolve_book_groups",
"resolve_hardlink_source",
"run_custom_script",
"safe_cleanup_path",
+12 -1
View File
@@ -52,7 +52,18 @@ def get_file_organization(*, is_audiobook: bool) -> str:
"""Get the file organization mode for the content type."""
key = "FILE_ORGANIZATION_AUDIOBOOK" if is_audiobook else "FILE_ORGANIZATION"
mode = _config_text(core_config.config.get(key, "rename")).strip().lower()
return mode if mode in ("none", "rename", "organize") else "rename"
return mode if mode in ("none", "rename", "rename_and_group", "organize") else "rename"
def get_word_separator() -> str:
"""Get the configured word separator for naming template values.
Replaces whitespace inside each placeholder's rendered value (e.g. "Conan
Doyle" -> "Conan.Doyle"). The setting holds the separator character
directly (e.g. "." or "_"); empty means a plain space, which leaves
values unchanged.
"""
return _config_text(core_config.config.get("NAMING_WORD_SEPARATOR", "")) or " "
def get_template(*, is_audiobook: bool, organization_mode: str) -> str:
+6 -3
View File
@@ -7,6 +7,7 @@ from pathlib import Path
from typing import TYPE_CHECKING
from shelfmark.core.logger import setup_logger
from shelfmark.core.utils import AUDIOBOOK_FORMATS
from shelfmark.core.utils import is_audiobook as check_audiobook
from shelfmark.download.archive import ArchiveExtractionError, extract_archive, is_archive
from shelfmark.download.fs import run_blocking_io
@@ -142,7 +143,7 @@ def scan_directory_tree(
is_audiobook = check_audiobook(content_type)
if is_audiobook:
trackable_exts = {".m4b", ".mp3", ".m4a", ".flac", ".ogg", ".wma", ".aac", ".wav"}
trackable_exts = {f".{fmt}" for fmt in AUDIOBOOK_FORMATS}
else:
trackable_exts = {
".pdf",
@@ -186,7 +187,9 @@ def scan_directory_tree(
file_path = Path(root) / filename
suffix = file_path.suffix.lower()
if suffix in supported_exts:
# zip/rar can also be enabled as supported formats. Keep archives
# out of book_files so they are extracted rather than imported whole.
if suffix in supported_exts and not is_archive(file_path):
book_files.append(file_path)
elif suffix in trackable_exts:
rejected_files.append(file_path)
@@ -367,7 +370,7 @@ def collect_staged_files(
is_audiobook = check_audiobook(task.content_type)
if is_audiobook:
trackable_exts = {".m4b", ".mp3", ".m4a", ".flac", ".ogg", ".wma", ".aac", ".wav"}
trackable_exts = {f".{fmt}" for fmt in AUDIOBOOK_FORMATS}
else:
trackable_exts = {
".pdf",
+165 -5
View File
@@ -2,6 +2,7 @@
from __future__ import annotations
import dataclasses
import os
from pathlib import Path
from typing import TYPE_CHECKING
@@ -12,18 +13,25 @@ from shelfmark.core.naming import (
assign_part_numbers,
build_library_path,
derive_primary_title,
normalize_language_code,
parse_naming_template,
sanitize_filename,
)
from shelfmark.core.utils import is_audiobook as check_audiobook
from shelfmark.download.archive import is_archive
from shelfmark.download.fs import (
atomic_copy,
atomic_hardlink,
atomic_move,
run_blocking_io,
)
from shelfmark.download.postprocess.policy import get_file_organization, get_template
from shelfmark.download.postprocess.policy import (
get_file_organization,
get_template,
get_word_separator,
)
from .packs import BookGroup, PackBook, group_files_into_books, match_plan_to_files
from .scan import collect_directory_files, scan_directory_tree
from .types import TransferPlan
from .workspace import safe_cleanup_path
@@ -63,6 +71,7 @@ def build_metadata_dict(task: DownloadTask) -> dict:
"Year": task.year,
"Series": task.series_name,
"SeriesPosition": task.series_position,
"Language": normalize_language_code(task.language),
"User": task.username,
}
@@ -158,6 +167,24 @@ def _transfer_single_file(
return atomic_move(source_path, dest_path, max_attempts=max_attempts), "move"
def _group_folder_name(source_root: Path | None) -> str:
"""Name the folder a grouped multi-file audiobook is transferred into.
A directory names the group directly. A file cannot hold several book files
on its own, so a non-directory source that produced more than one means
`collect_staged_files` extracted an archive: the stem is the release name and
the suffix is packaging, which is why `Book.zip` groups into `Book/` rather
than `Book.zip/` or, worse, not at all.
"""
if source_root is None:
return ""
if run_blocking_io(source_root.is_dir):
return sanitize_filename(source_root.name)
if is_archive(source_root):
return sanitize_filename(source_root.stem)
return ""
def transfer_book_files(
book_files: list[Path],
destination: Path,
@@ -167,6 +194,7 @@ def transfer_book_files(
is_torrent: bool,
preserve_source: bool = False,
organization_mode: str | None = None,
source_root: Path | None = None,
) -> tuple[list[Path], str | None, dict[str, int]]:
"""Transfer discovered book files into their final destination layout."""
if not book_files:
@@ -174,6 +202,20 @@ def transfer_book_files(
is_audiobook = check_audiobook(task.content_type)
organization_mode = organization_mode or get_file_organization(is_audiobook=is_audiobook)
word_separator = get_word_separator()
groups = resolve_book_groups(task, book_files, organization_mode=organization_mode)
if groups is not None:
return _transfer_book_groups(
groups,
destination,
task,
use_hardlink=use_hardlink,
is_torrent=is_torrent,
preserve_source=preserve_source,
organization_mode=organization_mode,
)
max_attempts = _max_attempts_for_batch(len(book_files))
final_paths: list[Path] = []
@@ -192,6 +234,7 @@ def transfer_book_files(
template,
file_metadata,
extension=ext or None,
word_separator=word_separator,
)
run_blocking_io(dest_path.parent.mkdir, parents=True, exist_ok=True)
@@ -219,6 +262,7 @@ def transfer_book_files(
template,
file_metadata,
extension=ext or None,
word_separator=word_separator,
)
run_blocking_io(dest_path.parent.mkdir, parents=True, exist_ok=True)
@@ -236,6 +280,13 @@ def transfer_book_files(
return final_paths, None, op_counts
transfer_destination = destination
if is_audiobook and len(book_files) > 1 and organization_mode == "rename_and_group":
source_folder = _group_folder_name(source_root)
if source_folder:
transfer_destination = destination / source_folder
run_blocking_io(transfer_destination.mkdir, parents=True, exist_ok=True)
for book_file in book_files:
if len(book_files) == 1 and organization_mode != "none":
if not task.format:
@@ -245,7 +296,9 @@ def transfer_book_files(
metadata = build_file_metadata(task, book_file)
extension = book_file.suffix.lstrip(".") or task.format or ""
filename = parse_naming_template(template, metadata, allow_path_separators=False)
filename = parse_naming_template(
template, metadata, allow_path_separators=False, word_separator=word_separator
)
filename = Path(filename).name if filename else ""
if filename and extension:
filename = f"{sanitize_filename(filename)}.{extension}"
@@ -254,7 +307,7 @@ def transfer_book_files(
else:
filename = book_file.name
dest_path = destination / filename
dest_path = transfer_destination / filename
final_path, op = _transfer_single_file(
book_file,
dest_path,
@@ -270,6 +323,101 @@ def transfer_book_files(
return final_paths, None, op_counts
def resolve_book_groups(
task: DownloadTask,
book_files: list[Path],
*,
organization_mode: str,
) -> list[BookGroup] | None:
"""Split a multi-book pack into per-book groups, or None to file as one book.
An approved `book_plan` wins; a bare `multi_book` flag falls back to heuristic
grouping. Organization `none` keeps files as-is, and a split that yields a single
group is not a pack at all.
"""
if organization_mode == "none" or not (task.book_plan or task.multi_book):
return None
if task.book_plan:
plan = [
PackBook(
title=str(entry.get("title") or ""),
series_position=entry.get("series_position"),
year=entry.get("year"),
files=list(entry.get("files") or []),
)
for entry in task.book_plan
if isinstance(entry, dict)
]
groups = match_plan_to_files(
plan, book_files, series_name=task.series_name, author_name=task.author
)
else:
groups = group_files_into_books(
book_files, series_name=task.series_name, author_name=task.author
)
return groups if len(groups) > 1 else None
def _transfer_book_groups(
groups: list[BookGroup],
destination: Path,
task: DownloadTask,
*,
use_hardlink: bool,
is_torrent: bool,
preserve_source: bool,
organization_mode: str,
) -> tuple[list[Path], str | None, dict[str, int]]:
"""Transfer each book of a pack through the normal single-book path.
Each book gets an isolated task copy (the single-file path mutates `task.format`)
carrying its own title, position and year; the searched book's position must not
leak onto its siblings, while author and series name apply to all of them.
"""
all_paths: list[Path] = []
totals: dict[str, int] = {"hardlink": 0, "copy": 0, "move": 0}
errors: list[str] = []
for group in groups:
book_task = dataclasses.replace(
task,
title=group.title or task.title,
year=str(group.year) if group.year is not None else None,
subtitle=None,
series_position=group.series_position,
multi_book=False,
book_plan=None,
)
paths, error, op_counts = transfer_book_files(
group.files,
destination,
book_task,
use_hardlink=use_hardlink,
is_torrent=is_torrent,
preserve_source=preserve_source,
organization_mode=organization_mode,
source_root=group.files[0].parent,
)
for op, count in op_counts.items():
totals[op] = totals.get(op, 0) + count
if error:
errors.append(f"{group.title}: {error}")
logger.warning("Task %s: pack book %r failed: %s", task.task_id, group.title, error)
continue
all_paths.extend(paths)
if not all_paths:
return [], "; ".join(errors) or "No book files found", totals
if errors:
logger.warning(
"Task %s: pack filed with %d failed book(s): %s",
task.task_id,
len(errors),
"; ".join(errors),
)
return all_paths, None, totals
def process_directory(
directory: Path,
ingest_dir: Path,
@@ -344,7 +492,12 @@ def transfer_file_to_library(
template_metadata = dict(metadata)
template_metadata.setdefault("OriginalName", source_path.stem)
dest_path = run_blocking_io(
build_library_path, library_base, template, template_metadata, extension
build_library_path,
library_base,
template,
template_metadata,
extension,
word_separator=get_word_separator(),
)
run_blocking_io(dest_path.parent.mkdir, parents=True, exist_ok=True)
@@ -399,12 +552,14 @@ def transfer_directory_to_library(
safe_cleanup_path(temp_file, task)
return None
word_separator = get_word_separator()
base_library_path = run_blocking_io(
build_library_path,
library_base,
template,
metadata,
extension=None,
word_separator=word_separator,
)
run_blocking_io(base_library_path.parent.mkdir, parents=True, exist_ok=True)
@@ -435,7 +590,12 @@ def transfer_directory_to_library(
ext = source_file.suffix.lstrip(".")
file_metadata = {**metadata, "PartNumber": part_number}
file_path = run_blocking_io(
build_library_path, library_base, template, file_metadata, extension=ext
build_library_path,
library_base,
template,
file_metadata,
extension=ext,
word_separator=word_separator,
)
run_blocking_io(file_path.parent.mkdir, parents=True, exist_ok=True)
+153
View File
@@ -0,0 +1,153 @@
"""Boot-time warm-up of the direct-download source.
The first AA search after a cold start pays for the whole cold path at once: DNS
resolution, electing a live mirror, spinning up headless Chrome and solving the
DDoS-Guard challenge. That is tens of seconds with the user sat at the search box.
Running one throwaway search shortly after boot moves that cost off the user's first
search. It primes the DNS cache, elects (and quarantines) mirrors, and leaves the
clearance cookie in the bypasser's per-domain cache, so the first real search reuses
it instead of solving from scratch.
Runs on a daemon thread and swallows every failure: this is an optimisation, and a
source that is down at boot must not affect startup or health.
"""
from __future__ import annotations
import os
import threading
from shelfmark.core.config import config
from shelfmark.core.logger import setup_logger
logger = setup_logger(__name__)
# Delay before the warm-up fires. Long enough that it does not compete with the rest
# of startup (and with a container's own health probe) for the first request.
_DEFAULT_DELAY_SECONDS = 15.0
_DEFAULT_QUERY = "The Great Gatsby"
_warmup_thread: threading.Thread | None = None
_warmup_lock = threading.Lock()
# Set as soon as a real release search starts. The warm-up exists to pay the cold path
# *before* the user does; once they have beaten it to the box there is nothing left to
# pre-solve, and running anyway is actively harmful - the bypasser serializes on one
# browser, so the warm-up's solve goes in front of the search the user is watching. In
# the bundle on issue #1276 that cost a full minute of a 2m27s wait, on a container 16
# seconds old, for a throwaway "The Great Gatsby" query nobody asked for.
_user_search_seen = threading.Event()
def note_user_search() -> None:
"""Record that a real search has run, so a pending warm-up stands down."""
_user_search_seen.set()
def _as_bool(value: object, *, default: bool) -> bool:
"""Coerce a config value that may arrive as a string, bool or None."""
if value is None:
return default
if isinstance(value, str):
from shelfmark.config.env import string_to_bool
return string_to_bool(value)
return bool(value)
def _setting(key: str, default: object) -> object:
"""Read a warm-up setting, preferring the deployment environment.
These keys are not in the settings registry, and ``config.get`` only consults the
environment for keys it knows about - so reading config alone silently ignored
SEARCH_WARMUP_ENABLED and always returned the default. Check os.environ first so
the documented switches actually work.
"""
raw = os.environ.get(key)
if raw is not None and raw.strip():
return raw
return config.get(key, default)
def is_enabled() -> bool:
"""Whether the boot-time warm-up search should run."""
if not _as_bool(_setting("SEARCH_WARMUP_ENABLED", True), default=True):
return False
# Nothing to warm if the source is off, and no challenge to pre-solve without
# the bypasser - a plain search is fast enough not to need this.
if not _as_bool(_setting("DIRECT_DOWNLOAD_ENABLED", True), default=True):
logger.debug("Search warm-up skipped: direct download disabled")
return False
return True
def warmup_query() -> str:
"""The query used to warm the source."""
raw = _setting("SEARCH_WARMUP_QUERY", _DEFAULT_QUERY)
query = str(raw).strip() if raw else ""
return query or _DEFAULT_QUERY
def run_warmup() -> bool:
"""Run one warm-up search. Returns True if it produced results.
Never raises: every failure mode here is one the next real search would hit
anyway, and reporting it is the search path's job, not the warm-up's.
"""
from shelfmark.core.mirrors import has_aa_mirror_configuration
# Checked here rather than only at schedule time: the delay is what this races with,
# so the user's first search usually lands *during* the wait, not before it.
if _user_search_seen.is_set():
logger.info("Search warm-up skipped: a real search got there first")
return False
if not has_aa_mirror_configuration():
logger.debug("Search warm-up skipped: no Anna's Archive mirrors configured")
return False
query = warmup_query()
logger.info("Warming up direct download search (%r)", query)
try:
from shelfmark.core.models import SearchFilters
from shelfmark.release_sources.direct_download import search_books
results, _ = search_books(query, SearchFilters())
except Exception:
# Broad by design: a warm-up must never take the app down, and the source
# raises everything from network errors to parse failures.
logger.warning("Search warm-up did not complete; first user search may be slow")
logger.debug("Search warm-up failure detail", exc_info=True)
return False
if results:
logger.info("Search warm-up complete: %s results, source is ready", len(results))
return True
logger.info("Search warm-up returned no results; source reachable but empty")
return False
def start(delay_seconds: float = _DEFAULT_DELAY_SECONDS) -> bool:
"""Schedule the warm-up on a daemon thread. Safe to call multiple times."""
global _warmup_thread
if not is_enabled():
return False
with _warmup_lock:
if _warmup_thread is not None and _warmup_thread.is_alive():
logger.debug("Search warm-up already scheduled")
return False
def _run() -> None:
run_warmup()
_warmup_thread = threading.Timer(delay_seconds, _run)
_warmup_thread.daemon = True
_warmup_thread.name = "SearchWarmup"
_warmup_thread.start()
logger.debug("Search warm-up scheduled in %ss", delay_seconds)
return True
+190 -82
View File
@@ -14,7 +14,7 @@ from importlib import import_module
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, NoReturn, cast
from flask import Flask, jsonify, request, send_file, send_from_directory, session
from flask import Flask, g, jsonify, request, send_file, send_from_directory, session
from flask_cors import CORS
from flask_socketio import SocketIO, emit
from werkzeug.middleware.proxy_fix import ProxyFix
@@ -38,7 +38,12 @@ from shelfmark.config.env import (
string_to_bool,
)
from shelfmark.config.security import _migrate_security_settings
from shelfmark.config.settings import _SUPPORTED_BOOK_LANGUAGE
from shelfmark.config.settings import (
_SUPPORTED_BOOK_LANGUAGE,
migrate_audiobook_format_settings,
)
from shelfmark.core import api_key as api_key_module # module access lets tests monkeypatch the key
from shelfmark.core import search_deadline
from shelfmark.core.activity_view_state_service import ActivityViewStateService
from shelfmark.core.auth_modes import (
get_auth_check_admin_status,
@@ -59,6 +64,7 @@ from shelfmark.core.notifications import (
notify_user,
)
from shelfmark.core.prefix_middleware import PrefixMiddleware
from shelfmark.core.release_inspect_routes import register_release_inspect_routes
from shelfmark.core.request_helpers import (
coerce_bool,
emit_ws_event,
@@ -80,8 +86,9 @@ from shelfmark.core.requests_service import (
sync_delivery_states_from_queue_status,
)
from shelfmark.core.user_db import UserDB
from shelfmark.core.utils import normalize_base_path
from shelfmark.core.utils import AUDIOBOOK_FORMATS, normalize_base_path
from shelfmark.download import orchestrator as backend
from shelfmark.download import warmup
from shelfmark.release_sources import (
BrowseRecord,
Release,
@@ -118,7 +125,7 @@ BASE_PATH = normalize_base_path(normalize_optional_text(app_config.get("URL_BASE
app = Flask(__name__)
app.config["SEND_FILE_MAX_AGE_DEFAULT"] = 0 # Disable caching
app.config["APPLICATION_ROOT"] = BASE_PATH or "/"
wsgi_app = cast(Any, ProxyFix(app.wsgi_app))
wsgi_app = cast(Any, ProxyFix(app.wsgi_app, x_host=1, x_port=1))
if BASE_PATH:
wsgi_app = cast(Any, PrefixMiddleware(wsgi_app, BASE_PATH, bypass_paths={"/api/health"}))
app.wsgi_app = wsgi_app
@@ -168,6 +175,9 @@ except ImportError as e:
# Migrate legacy security settings if needed
_migrate_security_settings()
# Widen audiobook formats for installs that still carry the old m4b/mp3-only default
migrate_audiobook_format_settings()
# Initialize user database and register multi-user routes
# If CONFIG_DIR doesn't exist or is read-only, multi-user features will be disabled
_user_db_path = str(Path(os.environ.get("CONFIG_DIR", "/config")) / "users.db")
@@ -200,6 +210,10 @@ except (sqlite3.OperationalError, OSError) as e:
# Start download coordinator
backend.start()
# Pre-solve the direct-download source's protection challenge in the background so the
# first user search does not pay for a cold Chrome bypass. Never blocks startup.
warmup.start()
# Rate limiting for login attempts
# Map usernames to their failed-attempt counters and lockout timestamps.
failed_login_attempts: dict[str, dict[str, Any]] = {}
@@ -319,19 +333,7 @@ def get_auth_mode() -> str:
_AUDIOBOOK_CATEGORY_RANGE = (3030, 3049)
_AUDIOBOOK_FORMAT_HINTS = frozenset(
{
"m4b",
"mp3",
"m4a",
"flac",
"ogg",
"wma",
"aac",
"wav",
"opus",
}
)
_AUDIOBOOK_FORMAT_HINTS = frozenset(AUDIOBOOK_FORMATS)
def _contains_audiobook_format_hint(value: Any) -> bool:
@@ -397,7 +399,7 @@ def _resolve_policy_mode_for_current_user(*, source: Any, content_type: Any) ->
auth_mode = get_auth_mode()
if auth_mode == "none":
return None
if session.get("is_admin", True):
if session.get("is_admin", False):
return None
if user_db is None:
return None
@@ -549,7 +551,7 @@ if _is_debug_enabled():
r"/*": {
"origins": ["http://localhost:5173", "http://127.0.0.1:5173"],
"supports_credentials": True,
"allow_headers": ["Content-Type", "Authorization"],
"allow_headers": ["Content-Type", "Authorization", "X-Api-Key"],
"methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
}
},
@@ -657,6 +659,73 @@ logger.info(
logger.info("Session cookie name: %s", SESSION_COOKIE_NAME)
def _proxy_default_is_admin(db: UserDB) -> bool:
"""Role for a proxy user seen for the first time when no admin group is configured.
The first account ever provisioned is an admin so the instance is never left without
one; later accounts follow PROXY_AUTH_DEFAULT_ROLE (default: user).
"""
if not db.has_admin():
return True
role = str(app_config.get("PROXY_AUTH_DEFAULT_ROLE", "user") or "user").strip().lower()
return role == "admin"
_API_KEY_EXEMPT_PREFIXES = ("/api/auth/",)
_API_KEY_EXEMPT_PATHS = frozenset({"/api/health"})
@app.before_request
def api_key_auth_middleware() -> Response | tuple[Response, int] | None:
"""Authenticate requests that present the configured SHELFMARK_API_KEY.
Both Authorization: Bearer and X-Api-Key are checked, and either
matching authenticates the request as an admin for this request only:
any session cookie is ignored and none is written back. Checking both
means a reverse proxy's own Authorization header never shadows an
operator-supplied X-Api-Key. No matching candidate is ignored so bearer
tokens forwarded by reverse proxies keep working; the request then
continues on the normal session path.
"""
if not request.path.startswith("/api/"):
return None
if request.path in _API_KEY_EXEMPT_PATHS or request.path.startswith(_API_KEY_EXEMPT_PREFIXES):
return None
if not api_key_module.SHELFMARK_API_KEY:
return None
candidates = api_key_module.extract_api_key_candidates(
request.headers.get("Authorization"), request.headers.get("X-Api-Key")
)
if not candidates:
return None
if not any(api_key_module.matches_api_key(candidate) for candidate in candidates):
return None
if get_auth_mode() == "none":
return None
# Mark the request as keyed before the lookup so the after-request cookie
# reset also covers the error path below.
g.api_key_auth = True
try:
admin = user_db.get_first_admin() if user_db is not None else None
except _OPERATIONAL_ERRORS:
logger.exception("API key auth middleware error")
return jsonify({"error": "Authentication error"}), 500
session.clear()
session["user_id"] = admin["username"] if admin else "api"
session["is_admin"] = True
if admin:
session["db_user_id"] = admin["id"]
session.permanent = False
# Identity is per request; never persist it as a cookie.
session.modified = False
return None
@app.before_request
def proxy_auth_middleware() -> Response | tuple[Response, int] | None:
"""Middleware to handle proxy authentication.
@@ -670,6 +739,10 @@ def proxy_auth_middleware() -> Response | tuple[Response, int] | None:
if auth_mode != "proxy":
return None
# A request already authenticated by SHELFMARK_API_KEY needs no proxy headers.
if g.get("api_key_auth"):
return None
# Skip for public endpoints that don't need auth
if request.path == "/api/health":
return None
@@ -709,8 +782,9 @@ def proxy_auth_middleware() -> Response | tuple[Response, int] | None:
# Resolve admin role for proxy sessions.
# If an admin group is configured, derive from groups header.
# Otherwise preserve existing DB role for known users and default
# first-time users to admin (to avoid lockouts).
# Otherwise preserve the existing DB role for known users; a first-time user
# is an admin only while the instance has none (so nobody is locked out),
# after that PROXY_AUTH_DEFAULT_ROLE decides (default: user).
admin_group_header = (
normalize_optional_text(
app_config.get("PROXY_AUTH_ADMIN_GROUP_HEADER", "X-Auth-Groups")
@@ -733,6 +807,8 @@ def proxy_auth_middleware() -> Response | tuple[Response, int] | None:
existing_db_user = user_db.get_user(username=username)
if existing_db_user:
is_admin = existing_db_user.get("role") == "admin"
else:
is_admin = _proxy_default_is_admin(user_db)
# Create or update session
previous_username = session.get("user_id")
@@ -799,6 +875,17 @@ def set_security_headers(response: Response) -> Response:
return response
@app.after_request
def strip_cookie_for_api_key_requests(response: Response) -> Response:
"""Keyed requests never mint or refresh a session cookie, even if a handler dirties the session."""
if g.get("api_key_auth"):
# Setting `permanent` mutates the session dict (re-marking it
# modified), so it must be reset before `modified`, not after.
session.permanent = False
session.modified = False
return response
def login_required(
f: Callable[..., Response | tuple[Response, int]],
) -> Callable[..., Response | tuple[Response, int]]:
@@ -1025,6 +1112,9 @@ def _serialize_release(release: Release) -> dict:
return result
register_release_inspect_routes(app, login_required)
@app.route("/api/releases/download", methods=["POST"])
@login_required
def api_download_release() -> Response | tuple[Response, int]:
@@ -1069,6 +1159,13 @@ def api_download_release() -> Response | tuple[Response, int]:
release_payload = dict(data)
release_payload["content_type"] = resolved_content_type
logger.info(
"Download request received. keys=%s downloads=%s extra.downloads=%s",
list(data.keys()),
data.get("downloads"),
data.get("extra", {}).get("downloads") if isinstance(data.get("extra"), dict) else None,
)
priority = data.get("priority", 0)
# Per-user download overrides
db_user_id = session.get("db_user_id")
@@ -1151,7 +1248,7 @@ def api_config() -> Response | tuple[Response, int]:
"build_version": BUILD_VERSION,
"release_version": RELEASE_VERSION,
"book_languages": _SUPPORTED_BOOK_LANGUAGE,
"default_language": app_config.BOOK_LANGUAGE,
"default_language": app_config.get("BOOK_LANGUAGE", ["en"], user_id=db_user_id),
"supported_formats": app_config.SUPPORTED_FORMATS,
"supported_audiobook_formats": app_config.SUPPORTED_AUDIOBOOK_FORMATS,
"search_mode": search_mode,
@@ -1176,12 +1273,18 @@ def api_config() -> Response | tuple[Response, int]:
[],
user_id=db_user_id,
),
# The client must not give up before this budget does. `/api/releases`
# answers a spent budget with a message naming the real cause (a protection
# challenge nobody could solve); a browser that aborted first replaces it
# with a generic network/proxy error and RELEASE_SEARCH_TIMEOUT becomes a
# setting the user can raise with no visible effect. See issue #1285.
"release_search_timeout": search_deadline.budget_seconds(),
"settings_enabled": _is_config_dir_writable(),
"onboarding_complete": _get_onboarding_complete(),
# Default sort orders
"default_sort": app_config.get(
"AA_DEFAULT_SORT", "relevance"
), # For direct mode (Anna's Archive)
"AA_DEFAULT_SORT", ""
), # For direct mode (Anna's Archive) — empty means use local downloads sort
"metadata_default_sort": get_provider_default_sort(
metadata_ui_provider
), # For universal mode
@@ -1344,6 +1447,7 @@ def _record_download_queued(task_id: str, task: Any) -> None:
size=normalize_optional_text(getattr(task, "size", None)),
preview=normalize_optional_text(getattr(task, "preview", None)),
content_type=normalize_optional_text(getattr(task, "content_type", None)),
downloads=getattr(task, "downloads", None),
origin=origin,
retry_payload=backend.serialize_task_for_retry(task),
)
@@ -1616,6 +1720,17 @@ def api_local_download() -> Response | tuple[Response, int]:
# Book data not found or not available
return jsonify({"error": "File not found"}), 404
is_admin, db_user_id, can_access_status = _resolve_status_scope()
if not is_admin:
actor_username = session.get("user_id")
if not can_access_status or not _task_owned_by_actor(
book_info,
actor_user_id=db_user_id,
actor_username=actor_username if isinstance(actor_username, str) else None,
):
return jsonify({"error": "File not found"}), 404
file_name = book_info.get_filename() if book_info is not None else Path(book_id).name
# Prepare the file for sending to the client
data = io.BytesIO(file_data)
@@ -2811,7 +2926,7 @@ def api_releases() -> Response | tuple[Response, int]:
try:
from dataclasses import asdict
from shelfmark.core.search_plan import build_release_search_plan
from shelfmark.core.release_search import search_source_releases
from shelfmark.metadata_providers import (
BookMetadata,
get_provider,
@@ -2830,53 +2945,17 @@ def api_releases() -> Response | tuple[Response, int]:
source_name: str, search_book: BookMetadata
) -> tuple[Any | None, list[Any], str | None]:
"""Search one source and return any error message instead of raising."""
try:
source = get_source(source_name)
plan = build_release_search_plan(
search_book,
languages=browse_filters.lang
if source_query_filters is not None
else languages,
manual_query=query_text if source_query_filters is not None else manual_query,
indexers=indexers,
source_filters=source_query_filters,
)
if plan.source_filters is not None:
planned_query = plan.manual_query or plan.primary_query
planned_query_type = "query"
elif plan.manual_query:
planned_query = plan.manual_query
planned_query_type = "manual"
elif not expand_search and plan.isbn_candidates:
planned_query = plan.isbn_candidates[0]
planned_query_type = "isbn"
else:
planned_query = plan.primary_query
planned_query_type = "title_author"
logger.debug(
"Searching %s: %s='%s' (title='%s', authors=%s, expand=%s, content_type=%s)",
source_name,
planned_query_type,
planned_query,
search_book.title,
search_book.authors,
expand_search,
content_type,
)
releases = source.search(
search_book, plan, expand_search=expand_search, content_type=content_type
)
except ValueError:
return None, [], f"Unknown source: {source_name}"
except (SourceUnavailableError, *_OPERATIONAL_ERRORS) as e:
logger.warning("Release search failed for source %s: %s", source_name, e)
return None, [], f"{source_name}: {e!s}"
else:
return source, releases, None
return search_source_releases(
source_name,
search_book,
languages=(browse_filters.lang if source_query_filters is not None else languages),
manual_query=(query_text if source_query_filters is not None else manual_query),
indexers=indexers,
expand_search=expand_search,
content_type=content_type,
source_filters=source_query_filters,
user_id=db_user_id,
)
provider = request.args.get("provider", "").strip()
book_id = request.args.get("book_id", "").strip()
@@ -2893,6 +2972,8 @@ def api_releases() -> Response | tuple[Response, int]:
if languages_param
else None
)
# Without an explicit filter the plan falls back to this user's default languages.
db_user_id = get_session_db_user_id(session)
# Content type for audiobook vs ebook search
content_type = request.args.get("content_type", "ebook").strip()
@@ -2940,6 +3021,10 @@ def api_releases() -> Response | tuple[Response, int]:
elif provider == "manual":
resolved_title = title_param or manual_query or "Manual Search"
resolved_author = author_param or ""
# The release modal sends `authors.join(', ')` as `author`, so the commas here
# are joins between contributors, not part of one name. This split is the only
# place that knows that, so `search_author` comes from it rather than from the
# joined text - see issue #1252.
authors = [a.strip() for a in resolved_author.split(",") if a.strip()]
book = BookMetadata(
@@ -2948,7 +3033,7 @@ def api_releases() -> Response | tuple[Response, int]:
provider_display_name="Manual Search",
title=resolved_title,
search_title=resolved_title,
search_author=resolved_author or None,
search_author=authors[0] if authors else None,
authors=authors,
)
else:
@@ -2981,18 +3066,36 @@ def api_releases() -> Response | tuple[Response, int]:
# Search only enabled sources
sources_to_search = [src["name"] for src in list_available_sources() if src["enabled"]]
# Search each source for releases
# Search each source for releases.
#
# Under a wall-clock budget: this endpoint is synchronous, and the bypass path it
# can reach used to be allowed minutes per URL with nothing bounding the request
# as a whole. A search that ran into an unsolvable protection challenge therefore
# outlived every reverse proxy in front of it and surfaced to the user as
# "Server unavailable (504)" - a gateway timeout that blames their proxy for a
# challenge failure. The budget is shared across sources, so a stuck first source
# cannot spend the whole request on its own. See issue #1276.
all_releases = []
errors = []
source_instances = {} # Keep source instances for column config
for source_name in sources_to_search:
source, releases, error = _search_source_releases(source_name, book)
if source is not None:
source_instances[source_name] = source
all_releases.extend(releases)
if error is not None:
errors.append(error)
# A real search is under way, so a warm-up still sitting on its start-up delay
# should stand down rather than queue its throwaway solve in front of this one.
warmup.note_user_search()
with search_deadline.search_deadline():
for source_name in sources_to_search:
if search_deadline.expired():
logger.warning("Release search budget spent; %s not searched", source_name)
errors.append(f"{source_name}: {search_deadline.deadline_message()}")
continue
source, releases, error = _search_source_releases(source_name, book)
if source is not None:
source_instances[source_name] = source
all_releases.extend(releases)
if error is not None:
errors.append(error)
# Convert Release objects to dicts
releases_data = [_serialize_release(release) for release in all_releases]
@@ -3017,8 +3120,13 @@ def api_releases() -> Response | tuple[Response, int]:
search_info = {}
for source_name, source_instance in source_instances.items():
info: dict[str, str | int | None] = {}
if hasattr(source_instance, "last_search_type") and source_instance.last_search_type:
search_info[source_name] = {"search_type": source_instance.last_search_type}
info["search_type"] = source_instance.last_search_type
if hasattr(source_instance, "total_results"):
info["total_results"] = source_instance.total_results
if info:
search_info[source_name] = info
response = {
"releases": releases_data,
+17 -14
View File
@@ -24,12 +24,12 @@ Dataclass representing a book from a metadata provider:
```python
@dataclass
class BookMetadata:
provider: str # Internal provider name (e.g., "hardcover")
provider_id: str # ID in that provider's system
provider: str # Internal provider name (e.g., "hardcover")
provider_id: str # ID in that provider's system
title: str
# Optional fields
provider_display_name: str # Human-readable name (e.g., "Hardcover")
provider_display_name: str # Human-readable name (e.g., "Hardcover")
authors: List[str]
isbn_10: str
isbn_13: str
@@ -39,7 +39,7 @@ class BookMetadata:
publish_year: int
language: str
genres: List[str]
source_url: str # Link to book on provider's site
source_url: str # Link to book on provider's site
display_fields: List[DisplayField] # Provider-specific display data
```
@@ -50,9 +50,9 @@ Provider-specific metadata for UI cards (ratings, page counts, reader counts, et
```python
@dataclass
class DisplayField:
label: str # e.g., "Rating", "Pages", "Readers"
value: str # e.g., "4.5", "496", "8,041"
icon: str # Icon name: "star", "book", "users", "editions"
label: str # e.g., "Rating", "Pages", "Readers"
value: str # e.g., "4.5", "496", "8,041"
icon: str # Icon name: "star", "book", "users", "editions"
```
### MetadataSearchOptions
@@ -64,7 +64,7 @@ Unified search options that work across all providers:
class MetadataSearchOptions:
query: str
search_type: SearchType = SearchType.GENERAL # GENERAL, TITLE, AUTHOR, ISBN
language: str = None # ISO 639-1 code (e.g., "en")
language: str = None # ISO 639-1 code (e.g., "en")
sort: SortOrder = SortOrder.RELEVANCE
limit: int = 40
page: int = 1
@@ -88,10 +88,10 @@ All providers must implement this interface:
```python
class MetadataProvider(ABC):
name: str # Internal identifier
display_name: str # Human-readable name
requires_auth: bool # True if API key required
supported_sorts: List[SortOrder] # Supported sort options
name: str # Internal identifier
display_name: str # Human-readable name
requires_auth: bool # True if API key required
supported_sorts: List[SortOrder] # Supported sort options
@abstractmethod
def search(self, options: MetadataSearchOptions) -> List[BookMetadata]:
@@ -121,9 +121,9 @@ class MetadataProvider(ABC):
```python
from shelfmark.metadata_providers import register_provider
@register_provider("my_provider")
class MyProvider(MetadataProvider):
...
class MyProvider(MetadataProvider): ...
```
### Getting Providers
@@ -281,11 +281,13 @@ from shelfmark.config.env import (
METADATA_CACHE_BOOK_TTL,
)
@cacheable(ttl=METADATA_CACHE_SEARCH_TTL, key_prefix="myprovider:search")
def _search_cached(self, cache_key: str, options: MetadataSearchOptions):
# Cached search implementation
pass
@cacheable(ttl=METADATA_CACHE_BOOK_TTL, key_prefix="myprovider:book")
def get_book(self, book_id: str):
# Cached book lookup
@@ -302,6 +304,7 @@ from shelfmark.metadata_providers.openlibrary import RateLimiter
# 90 requests per 60 seconds
rate_limiter = RateLimiter(max_requests=90, window_seconds=60)
def make_request(self):
rate_limiter.wait_if_needed() # Blocks if rate limited
# ... make request
+3
View File
@@ -709,3 +709,6 @@ with suppress(ImportError):
with suppress(ImportError):
from shelfmark.metadata_providers import googlebooks as googlebooks
with suppress(ImportError):
from shelfmark.metadata_providers import moly as moly
+3 -2
View File
@@ -158,10 +158,11 @@ class GoogleBooksProvider(MetadataProvider):
query = "+".join(query_parts)
# Build request params
page_size = min(options.limit, 40) # Google max is 40
params: dict[str, Any] = {
"q": query,
"maxResults": min(options.limit, 40), # Google max is 40
"startIndex": (options.page - 1) * options.limit,
"maxResults": page_size,
"startIndex": (options.page - 1) * page_size,
"printType": "books", # Exclude magazines
}
+126 -19
View File
@@ -1,6 +1,7 @@
"""Hardcover.app metadata provider. Requires API key."""
import re
import time
from contextlib import suppress
from dataclasses import dataclass
from datetime import UTC, datetime
@@ -47,6 +48,10 @@ HARDCOVER_PAGE_SIZE = 25 # Hardcover API returns max 25 results per page
HARDCOVER_MIN_AUTHOR_PARTS = 2
HARDCOVER_MIN_TYPEAHEAD_QUERY_LENGTH = 2
HARDCOVER_MAX_SERIES_OPTIONS = 7
# Hardcover hands out short opaque tokens now ("hc_pat_...") instead of the ~500 char
# JWTs it used to, so the length floor only applies to keys without that prefix.
HARDCOVER_API_KEY_PREFIX = "hc_pat_"
HARDCOVER_BEARER_PREFIX_PATTERN = re.compile(r"^bearer\s+", re.IGNORECASE)
HARDCOVER_API_KEY_MIN_LENGTH = 100
HARDCOVER_LIST_URL_PATTERN = re.compile(
r"^/(?:@([\w.-]+)/)?lists?/([\w-]+)/?$",
@@ -317,6 +322,7 @@ query SearchFieldOptions(
fields: $fields,
weights: $weights
) {
error
results
}
}
@@ -535,13 +541,19 @@ SORT_MAPPING: dict[SortOrder, str] = {
SortOrder.OLDEST: "release_year:asc",
}
# Mapping from abstract search type to Hardcover fields parameter
SEARCH_TYPE_FIELDS: dict[SearchType, str] = {
SearchType.GENERAL: "title,isbns,series_names,author_names,alternative_titles",
SearchType.TITLE: "title,alternative_titles",
SearchType.AUTHOR: "author_names",
# ISBN is handled separately via search_by_isbn()
}
# `fields` becomes Typesense's `query_by`, but Hardcover keeps `num_typos` and
# `query_by_weights` as fixed-length presets per query_type. Passing a different
# number of fields than the preset expects makes Typesense reject the whole search,
# complaining that the number of num_typos values does not match the number of
# query_by fields. So a Book search may only ever narrow to *these five* names --
# a shorter list is rejected outright rather than searched, and any weights sent
# alongside must match one-for-one.
# Weights only bias ranking: a field weighted 0 still matches, so `fields` can no
# longer restrict which fields a Book query looks at.
BOOK_SEARCH_FIELDS = "title,alternative_titles,author_names,series_names,isbns"
BOOK_SEARCH_FIELD_COUNT = 5
BOOK_TITLE_WEIGHTS = "5,1,0,0,0"
BOOK_TITLE_AUTHOR_WEIGHTS = "5,1,3,0,0"
SERIES_SEARCH_FIELDS = "name,books,author_name"
SERIES_SEARCH_WEIGHTS = "2,1,1"
@@ -549,10 +561,52 @@ SERIES_SEARCH_SORT = "_text_match:desc,readers_count:desc"
AUTHOR_SUGGESTION_FIELDS = "name,name_personal,alternate_names"
AUTHOR_SUGGESTION_WEIGHTS = "4,3,2"
AUTHOR_SUGGESTION_SORT = "_text_match:desc,books_count:desc"
TITLE_SUGGESTION_FIELDS = "title,alternative_titles"
TITLE_SUGGESTION_WEIGHTS = "5,2"
TITLE_SUGGESTION_FIELDS = BOOK_SEARCH_FIELDS
TITLE_SUGGESTION_WEIGHTS = "5,2,0,0,0"
TITLE_SUGGESTION_SORT = "_text_match:desc,users_count:desc"
# Hardcover forwards `sort` to Typesense's `sort_by` and rejects the whole search
# if it does not like the value -- an unknown field, a bare field name with no
# direction, more than three keys. A rejected search comes back as HTTP 200 with
# no GraphQL errors and a null `results` body, which is otherwise indistinguishable
# from "nothing matched"; the reason only shows up in the sibling `error` field,
# so every search asks for it. Dropping `sort` from the request is the one shape
# Hardcover always accepts -- an empty string is a value like any other and has
# been rejected too -- so retry that way and keep the fallback sticky for a while
# rather than paying for a doomed request on every search.
SORT_FALLBACK_TTL = 900.0
_sort_fallback_until = 0.0
def _without_sort(variables: dict[str, Any]) -> dict[str, Any]:
"""Drop `sort` entirely so Hardcover applies its own default ordering."""
return {key: value for key, value in variables.items() if key != "sort"}
def _search_payload_rejected(result: dict[str, Any] | None) -> bool:
"""Report whether Hardcover answered a search with a null results body.
A search that genuinely matched nothing still returns a results object with
``found: 0``; only a rejected search nulls it out entirely.
"""
if not isinstance(result, dict):
return False
root = result.get("search", result)
if not isinstance(root, dict) or "results" not in root:
return False
return root["results"] is None
def _search_rejection_reason(result: dict[str, Any] | None) -> str:
"""Return Hardcover's explanation for a rejected search, if it sent one."""
if not isinstance(result, dict):
return ""
root = result.get("search", result)
if not isinstance(root, dict):
return ""
error = root.get("error")
return error.strip() if isinstance(error, str) else ""
def _combine_headline_description(headline: str | None, description: str | None) -> str | None:
"""Combine headline (tagline) and description into a single description."""
@@ -620,7 +674,7 @@ def _normalize_series_position(value: Any) -> float | None:
def _normalize_hardcover_api_key(value: object) -> str:
"""Normalize Hardcover API keys, stripping copied auth-header prefixes."""
normalized_value = normalize_optional_text(value) or ""
return normalized_value.removeprefix("Bearer ").strip()
return HARDCOVER_BEARER_PREFIX_PATTERN.sub("", normalized_value.strip()).strip()
def _normalize_search_text(value: str) -> str:
@@ -986,13 +1040,15 @@ class HardcoverProvider(MetadataProvider):
"""Build search query, fields, and weights based on provided values.
Returns (query, fields, weights) tuple. Fields/weights are None for general search.
A narrowed search still sends all of BOOK_SEARCH_FIELDS -- Hardcover rejects a
shorter list outright -- and leans on the weights to rank the wanted field first.
"""
if author and not title and not series:
return author, None, None
if title and not author and not series:
return title, "title,alternative_titles", "5,1"
return title, BOOK_SEARCH_FIELDS, BOOK_TITLE_WEIGHTS
if author and title and not series:
return f"{title} {author}", "title,alternative_titles,author_names", "5,1,3"
return f"{title} {author}", BOOK_SEARCH_FIELDS, BOOK_TITLE_AUTHOR_WEIGHTS
return default_query, None, None
def _detect_list_url(self, query: str) -> tuple[str | None, str] | None:
@@ -1200,7 +1256,7 @@ class HardcoverProvider(MetadataProvider):
if not self.api_key or len(normalized_query) < HARDCOVER_MIN_TYPEAHEAD_QUERY_LENGTH:
return []
result = self._execute_query(
result = self._execute_search_query(
SEARCH_FIELD_OPTIONS_QUERY,
{
"query": normalized_query,
@@ -1431,7 +1487,7 @@ class HardcoverProvider(MetadataProvider):
logger.debug("Invalid Hardcover series id field value: %s", normalized_value)
return None
result = self._execute_query(
result = self._execute_search_query(
SEARCH_FIELD_OPTIONS_QUERY,
{
"query": normalized_value,
@@ -2358,6 +2414,7 @@ class HardcoverProvider(MetadataProvider):
graphql_query = """
query SearchBooks($query: String!, $limit: Int!, $page: Int!, $sort: String, $fields: String, $weights: String) {
search(query: $query, query_type: "Book", per_page: $limit, page: $page, sort: $sort, fields: $fields, weights: $weights) {
error
results
}
}
@@ -2366,6 +2423,7 @@ class HardcoverProvider(MetadataProvider):
graphql_query = """
query SearchBooks($query: String!, $limit: Int!, $page: Int!, $sort: String) {
search(query: $query, query_type: "Book", per_page: $limit, page: $page, sort: $sort) {
error
results
}
}
@@ -2386,7 +2444,7 @@ class HardcoverProvider(MetadataProvider):
variables["weights"] = search_weights
try:
result = self._execute_query(graphql_query, variables)
result = self._execute_search_query(graphql_query, variables)
if not result:
logger.debug("Hardcover search: No result from API")
return SearchResult(books=[], page=options.page, total_found=0, has_more=False)
@@ -2654,6 +2712,54 @@ class HardcoverProvider(MetadataProvider):
raise RuntimeError(msg) from e
return None
def _execute_search_query(self, query: str, variables: dict[str, Any]) -> dict | None:
"""Execute a search query, retrying without ``sort`` if Hardcover rejects it.
Returns None when the search was rejected, so callers report an empty
result rather than silently treating a failure as "nothing matched".
"""
global _sort_fallback_until
sort = variables.get("sort")
if sort and time.monotonic() < _sort_fallback_until:
variables = _without_sort(variables)
sort = None
result = self._execute_query(query, variables)
if not _search_payload_rejected(result):
return result
reason = _search_rejection_reason(result)
if not sort:
logger.error(
"Hardcover rejected this search (query_type=%s, fields=%s): %s",
variables.get("queryType", "Book"),
variables.get("fields"),
reason or "no error message",
)
return None
retry = self._execute_query(query, _without_sort(variables))
if _search_payload_rejected(retry):
# The sort was not the culprit, so leave sorting alone for other searches.
logger.error(
"Hardcover rejected this search (query_type=%s, fields=%s) with and without "
"a sort order: %s",
variables.get("queryType", "Book"),
variables.get("fields"),
_search_rejection_reason(retry) or reason or "no error message",
)
return None
logger.warning(
"Hardcover rejected sort '%s' (%s); dropping the sort order from searches for %ss",
sort,
reason or "no error message",
int(SORT_FALLBACK_TTL),
)
_sort_fallback_until = time.monotonic() + SORT_FALLBACK_TTL
return retry
def _parse_search_result(self, item: dict) -> BookMetadata | None:
"""Parse a search result item into BookMetadata."""
try:
@@ -2917,12 +3023,13 @@ def _test_hardcover_connection(current_values: dict[str, Any] | None = None) ->
_save_connected_user(None, None)
return {"success": False, "message": "API key is required"}
if key_len < HARDCOVER_API_KEY_MIN_LENGTH:
is_prefixed_key = api_key.startswith(HARDCOVER_API_KEY_PREFIX)
if not is_prefixed_key and key_len < HARDCOVER_API_KEY_MIN_LENGTH:
return {
"success": False,
"message": (
f"API key seems too short ({key_len} chars). "
f"Expected {HARDCOVER_API_KEY_MIN_LENGTH}+ chars."
f"API key seems too short ({key_len} chars). Expected a key starting "
f"with {HARDCOVER_API_KEY_PREFIX} or {HARDCOVER_API_KEY_MIN_LENGTH}+ chars."
),
}
@@ -3029,7 +3136,7 @@ def hardcover_settings() -> list[SettingsField]:
PasswordField(
key="HARDCOVER_API_KEY",
label="API Key",
description="Get your API key from hardcover.app/account/api",
description="Get your API key from hardcover.app/account/api (starts with hc_pat_)",
required=True,
),
ActionButton(
+493
View File
@@ -0,0 +1,493 @@
"""Moly.hu metadata provider. Hungarian book catalog, no API key required.
Scraping approach (search URL, book-page structure, language mapping) adapted
from the Calibre Moly_hu plugin by Hoffer Csaba, Kloon, otapi, Dezso, Hokutya,
seeder and contributors (GPL v3, mobileread.com).
"""
import re
import threading
import time
import unicodedata
from collections import deque
from typing import Any, ClassVar
from urllib.parse import quote
import requests
from bs4 import BeautifulSoup, Tag
from shelfmark.core.cache import cacheable
from shelfmark.core.logger import setup_logger
from shelfmark.core.settings_registry import (
ActionButton,
CheckboxField,
HeadingField,
SettingsField,
register_settings,
)
from shelfmark.download.network import get_ssl_verify
from shelfmark.metadata_providers import (
BookMetadata,
DisplayField,
MetadataProvider,
MetadataSearchOptions,
SearchField,
SearchType,
SortOrder,
TextSearchField,
register_provider,
)
logger = setup_logger(__name__)
MOLY_BASE_URL = "https://moly.hu"
MOLY_BOOK_URL = f"{MOLY_BASE_URL}/konyvek/"
MOLY_SEARCH_URL = f"{MOLY_BASE_URL}/kereses?query="
# Be polite: moly.hu is a small community site
RATE_LIMIT_REQUESTS = 30
RATE_LIMIT_WINDOW_SECONDS = 60
REQUEST_HEADERS = {
"User-Agent": ("Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"),
"Accept-Language": "hu,en;q=0.7",
}
ISBN_13_LENGTH = 13
# Moly tags its foreign-language editions; everything else is Hungarian.
# Mapping from the Calibre Moly_hu plugin.
_LANGUAGE_TAG_MAP = {
"angol nyelvű": "en",
"n\xe9met nyelvű": "de",
"francia nyelvű": "fr",
"olasz nyelvű": "it",
"spanyol nyelvű": "es",
"orosz nyelvű": "ru",
"t\xf6r\xf6k nyelvű": "tr",
"g\xf6r\xf6g nyelvű": "el",
"k\xednai nyelvű": "zh",
"jap\xe1n nyelvű": "ja",
}
class RateLimiter:
"""Simple sliding window rate limiter."""
def __init__(self, max_requests: int, window_seconds: int) -> None:
"""Initialize rate limiter with max requests per time window."""
self.max_requests = max_requests
self.window_seconds = window_seconds
self.timestamps: deque[float] = deque()
self.lock = threading.Lock()
def wait_if_needed(self) -> None:
"""Block until a request is allowed (thread-safe)."""
wait_time = 0.0
with self.lock:
now = time.time()
cutoff = now - self.window_seconds
while self.timestamps and self.timestamps[0] < cutoff:
self.timestamps.popleft()
if len(self.timestamps) >= self.max_requests:
wait_time = self.timestamps[0] + self.window_seconds - now
if wait_time > 0:
logger.debug("Rate limited, waiting %0.2fs", wait_time)
time.sleep(wait_time)
with self.lock:
now = time.time()
cutoff = now - self.window_seconds
while self.timestamps and self.timestamps[0] < cutoff:
self.timestamps.popleft()
self.timestamps.append(time.time())
_rate_limiter = RateLimiter(RATE_LIMIT_REQUESTS, RATE_LIMIT_WINDOW_SECONDS)
def _clean_text(value: str | None) -> str | None:
"""Strip zero-width characters and collapse whitespace."""
if value is None:
return None
value = value.replace("​", "").replace("", "")
return " ".join(value.split())
def _normalize_for_match(value: str | None) -> str:
"""Accent-insensitive, punctuation-insensitive comparison form."""
if not value:
return ""
value = unicodedata.normalize("NFKD", value)
value = "".join(char for char in value if not unicodedata.combining(char))
value = "".join(char if char.isalnum() else " " for char in value)
return " ".join(value.lower().split())
def _absolute_url(url: str | None) -> str | None:
if not url:
return None
if url.startswith(("http://", "https://")):
return url
return MOLY_BASE_URL + url
def _valid_isbn(candidate: str) -> str | None:
"""Return a normalized ISBN-10/13 (digits, with optional X check digit), else None."""
digits = candidate.replace("-", "").strip()
if len(digits) == ISBN_13_LENGTH and digits.isdigit():
return digits
if len(digits) == 10 and re.fullmatch(r"\d{9}[\dXx]", digits):
return digits.upper()
return None
@register_provider("moly")
class MolyProvider(MetadataProvider):
"""Moly.hu metadata provider (HTML scraping, Hungarian catalog)."""
name = "moly"
display_name = "Moly.hu"
requires_auth = False
supported_sorts: ClassVar[tuple[SortOrder, ...]] = (SortOrder.RELEVANCE,)
search_fields: ClassVar[tuple[SearchField, ...]] = (
TextSearchField(
key="author",
label="Author",
description="Search by author name",
),
TextSearchField(
key="title",
label="Title",
description="Search by book title",
),
)
def __init__(self) -> None:
"""Initialize provider."""
self.session = requests.Session()
self.session.headers.update(REQUEST_HEADERS)
def is_available(self) -> bool:
"""Moly.hu needs no authentication."""
return True
def _fetch(self, url: str, timeout: int = 15) -> str | None:
_rate_limiter.wait_if_needed()
try:
response = self.session.get(url, timeout=timeout, verify=get_ssl_verify(MOLY_BASE_URL))
response.raise_for_status()
except requests.Timeout:
logger.warning("Moly.hu request timed out: %s", url)
return None
except requests.RequestException:
logger.exception("Moly.hu request failed: %s", url)
return None
return response.text
def search(self, options: MetadataSearchOptions) -> list[BookMetadata]:
"""Search moly.hu's site search."""
if options.search_type == SearchType.ISBN:
result = self.search_by_isbn(options.query)
return [result] if result else []
# Moly's search is a single ranked page; no server-side pagination.
if options.page > 1:
return []
author_value = (options.fields.get("author") or "").strip()
title_value = (options.fields.get("title") or "").strip()
terms = " ".join(t for t in (author_value, title_value) if t)
query = terms or options.query.strip()
if not query:
return []
fields_key = ":".join(f"{k}={v}" for k, v in sorted(options.fields.items()))
cache_key = f"{query}:{options.search_type.value}:{options.limit}:{fields_key}"
return self._search_cached(cache_key, query, options.limit) or []
@cacheable(ttl_key="METADATA_CACHE_SEARCH_TTL", ttl_default=300, key_prefix="moly:search")
def _search_cached(self, cache_key: str, query: str, limit: int) -> list[BookMetadata] | None:
# Return None (not []) on fetch failure so the failure is not cached.
html = self._fetch(MOLY_SEARCH_URL + quote(query.encode("utf-8")))
if html is None:
return None
soup = BeautifulSoup(html, "html.parser")
books: list[BookMetadata] = []
seen: set[str] = set()
for anchor in soup.select("#content div.search_area a.book_selector"):
href = anchor.get("href") or ""
match = re.search(r"/konyvek/([^/?#]+)", str(href))
if not match:
continue
slug = match.group(1)
if slug in seen:
continue
# No separator: moly wraps matched search terms in <strong> even
# mid-word ("Lis<strong>a</strong> Jewell"), so inserting one
# would split words at highlight boundaries.
text = _clean_text(anchor.get_text()) or ""
author, _, title = text.partition(":")
if not title:
# Result rows are "Author: Title"; skip anything else.
continue
author = author.strip()
title = title.strip()
seen.add(slug)
books.append(
BookMetadata(
provider=self.name,
provider_id=slug,
provider_display_name=self.display_name,
title=title,
authors=[author] if author else [],
cover_url=self._cover_for_result(soup, text),
source_url=MOLY_BOOK_URL + slug,
language="hu",
search_title=title,
search_author=author or None,
display_fields=self._result_display_fields(anchor),
)
)
if len(books) >= limit:
break
logger.info("Moly.hu search '%s' returned %s results", query, len(books))
return books
def _cover_for_result(self, soup: BeautifulSoup, result_text: str) -> str | None:
"""Find the search-result thumbnail whose alt matches 'Author: Title'."""
target = _normalize_for_match(result_text)
if not target:
return None
for img in soup.select("#content img.tooltip[alt]"):
if _normalize_for_match(str(img.get("alt") or "")) == target:
return _absolute_url(str(img.get("src") or "")) or None
return None
def _result_display_fields(self, anchor: Tag) -> list[DisplayField]:
fields: list[DisplayField] = []
parent = anchor.parent
if parent is None:
return fields
like = parent.select_one("span.like_count")
if like:
fields.append(
DisplayField(label="Rating", value=like.get_text(strip=True), icon="star")
)
series = parent.select_one('a[href*="/sorozatok/"]')
if series:
fields.append(
DisplayField(
label="Series",
value=series.get_text(strip=True).strip("()"),
icon="editions",
)
)
return fields
@cacheable(ttl_key="METADATA_CACHE_BOOK_TTL", ttl_default=600, key_prefix="moly:book")
def get_book(self, book_id: str) -> BookMetadata | None:
"""Get book details by moly.hu slug (e.g. 'mocsidzuki-mai-a-telihold-kavezo')."""
html = self._fetch(MOLY_BOOK_URL + quote(book_id))
if html is None:
return None
soup = BeautifulSoup(html, "html.parser")
title = self._parse_title(soup)
authors = [_clean_text(a.get_text()) or "" for a in soup.select("#content div.authors a")]
authors = [a for a in authors if a]
if not title or not authors:
logger.warning("Moly.hu book page missing title/authors: %s", book_id)
return None
isbn_13, isbn_10 = self._parse_isbns(soup)
series = self._parse_series(soup)
tags = [_clean_text(t.get_text()) or "" for t in soup.select("#book_tags a.tag")]
tags = [t for t in tags if t]
display_fields: list[DisplayField] = []
rating = soup.select_one("#content .rating .like_count")
if rating:
display_fields.append(
DisplayField(label="Rating", value=rating.get_text(strip=True), icon="star")
)
if series:
display_fields.append(DisplayField(label="Series", value=series, icon="editions"))
return BookMetadata(
provider=self.name,
provider_id=book_id,
provider_display_name=self.display_name,
title=title,
authors=authors,
isbn_13=isbn_13,
isbn_10=isbn_10,
cover_url=self._parse_cover(soup),
description=self._parse_description(soup),
publisher=self._parse_publisher(soup),
publish_year=self._parse_publish_year(soup),
language=self._parse_language(tags),
genres=tags,
source_url=MOLY_BOOK_URL + book_id,
search_title=title,
search_author=authors[0],
display_fields=display_fields,
)
@cacheable(ttl_key="METADATA_CACHE_BOOK_TTL", ttl_default=600, key_prefix="moly:isbn")
def search_by_isbn(self, isbn: str) -> BookMetadata | None:
"""Moly's site search resolves ISBN queries directly."""
isbn = isbn.replace("-", "").strip()
if not isbn:
return None
html = self._fetch(MOLY_SEARCH_URL + quote(isbn))
if html is None:
return None
soup = BeautifulSoup(html, "html.parser")
anchor = soup.select_one("#content div.search_area a.book_selector[href]")
if not anchor:
return None
match = re.search(r"/konyvek/([^/?#]+)", str(anchor.get("href")))
if not match:
return None
return self.get_book(match.group(1))
def _parse_title(self, soup: BeautifulSoup) -> str | None:
node = soup.select_one("#content .head_title h1 span.item")
if node:
# The series link is nested inside this span; only direct text
# belongs to the book title.
direct = "".join(node.find_all(string=True, recursive=False))
title = _clean_text(direct)
if title:
return title
node = soup.select_one("#content .book > span")
if node:
return _clean_text(node.get_text())
return None
def _parse_series(self, soup: BeautifulSoup) -> str | None:
node = soup.select_one('#content h1 a[href*="/sorozatok/"]')
if not node:
return None
return (_clean_text(node.get_text()) or "").strip("()") or None
def _parse_isbns(self, soup: BeautifulSoup) -> tuple[str | None, str | None]:
isbn_13 = isbn_10 = None
editions = soup.select("#content .items .edition") or soup.select("#content .items > div")
for edition in editions:
text = edition.get_text(" ")
for candidate in re.findall(r"(?<!\d)[\d-]{10,17}(?!\d)", text):
isbn = _valid_isbn(candidate)
if not isbn:
continue
if len(isbn) == ISBN_13_LENGTH and not isbn_13:
isbn_13 = isbn
elif len(isbn) != ISBN_13_LENGTH and not isbn_10:
isbn_10 = isbn
if isbn_13:
break
return isbn_13, isbn_10
def _parse_cover(self, soup: BeautifulSoup) -> str | None:
node = soup.select_one("#content .coverbox a.zoom[href]")
if node:
return _absolute_url(str(node.get("href")))
img = soup.select_one("#content .coverbox img[src]")
if img:
return _absolute_url(str(img.get("src")))
return None
def _parse_description(self, soup: BeautifulSoup) -> str | None:
node = soup.select_one("#content #full_description")
if node is None:
node = soup.select_one("#content div.text")
if node is None:
return None
spoiler_warning = "Vigyázat! Cselekményleírást tartalmaz."
parts = []
for text in node.stripped_strings:
cleaned = _clean_text(text) or ""
if cleaned.startswith(spoiler_warning):
cleaned = cleaned[len(spoiler_warning) :].strip()
if cleaned:
parts.append(cleaned)
return "\n".join(parts) or None
def _parse_publisher(self, soup: BeautifulSoup) -> str | None:
node = soup.select_one('#content .items .edition a[href*="/kiadok/"]')
if node:
return _clean_text(node.get_text())
return None
def _parse_publish_year(self, soup: BeautifulSoup) -> int | None:
editions = soup.select("#content .items .edition") or soup.select("#content .items > div")
for edition in editions:
match = re.search(r"\b(\d{4})\b", edition.get_text(" "))
if match:
return int(match.group(1))
return None
def _parse_language(self, tags: list[str]) -> str:
for tag in tags:
code = _LANGUAGE_TAG_MAP.get(tag.lower().strip())
if code:
return code
return "hu"
def _test_moly_connection() -> dict[str, Any]:
"""Test connectivity to moly.hu."""
try:
provider = MolyProvider()
response = provider.session.get(
MOLY_SEARCH_URL + quote("teszt"),
timeout=10,
verify=get_ssl_verify(MOLY_BASE_URL),
)
response.raise_for_status()
except requests.Timeout:
return {"success": False, "message": "Connection timed out"}
except requests.RequestException as e:
return {"success": False, "message": f"Connection failed: {e}"}
if "moly" in response.text.lower():
return {"success": True, "message": "Successfully connected to moly.hu"}
return {"success": False, "message": "Unexpected response from moly.hu"}
@register_settings("moly", "Moly.hu", icon="library", order=54, group="metadata_providers")
def moly_settings() -> list[SettingsField]:
"""Moly.hu metadata provider settings."""
return [
HeadingField(
key="moly_heading",
title="Moly.hu",
description=(
"Hungarian community book catalog with excellent coverage of "
"Hungarian editions and translations. No API key required."
),
link_url="https://moly.hu",
link_text="moly.hu",
),
CheckboxField(
key="MOLY_ENABLED",
label="Enable Moly.hu",
description="Enable Moly.hu as a metadata provider for book searches",
default=False,
),
ActionButton(
key="test_connection",
label="Test Connection",
description="Verify moly.hu is accessible",
style="primary",
callback=_test_moly_connection,
),
]
+34 -4
View File
@@ -13,6 +13,7 @@ if TYPE_CHECKING:
from shelfmark.core.models import DownloadTask
from shelfmark.core.search_plan import ReleaseSearchPlan
from shelfmark.download.postprocess.packs import PackFile
from shelfmark.metadata_providers import BookMetadata
@@ -45,6 +46,7 @@ class BrowseRecord:
content: str | None = None
format: str | None = None
size: str | None = None
downloads: int | None = None
info: dict[str, list[str]] | None = None
description: str | None = None
download_urls: list[str] = field(default_factory=list)
@@ -163,6 +165,7 @@ class SortOption:
label: str # Display label in the sort dropdown
sort_key: str # Field to sort by on the Release object
default_direction: Literal["asc", "desc"] = "desc" # Which way "best first" runs
@dataclass
@@ -261,7 +264,12 @@ def serialize_column_config(config: ReleaseColumnConfig) -> dict[str, Any]:
# Include extra sort options (sort entries not tied to a column)
if config.extra_sort_options:
result["extra_sort_options"] = [
{"label": opt.label, "sort_key": opt.sort_key} for opt in config.extra_sort_options
{
"label": opt.label,
"sort_key": opt.sort_key,
"default_direction": opt.default_direction,
}
for opt in config.extra_sort_options
]
# Include action button if specified (replaces default expand search)
@@ -361,12 +369,21 @@ class ReleaseSource(ABC):
return None
@dataclass(frozen=True)
class HandoffResult:
"""An external handoff that completed without a Shelfmark book payload."""
path: str
message: str
class DownloadHandler(ABC):
"""Interface for executing downloads.
A handler may either:
- download directly into ``TMP_DIR`` (managed by Shelfmark), or
- return a path owned by an external client (e.g. torrent/usenet).
- finish an external handoff without producing a book payload.
The orchestrator is responsible for post-processing (archive extraction, output mode
handling) and transferring files into their final destination.
@@ -379,8 +396,8 @@ class DownloadHandler(ABC):
cancel_flag: Event,
progress_callback: Callable[[float], None],
status_callback: Callable[[str, str | None], None],
) -> str | None:
"""Execute download and return a path to the downloaded payload."""
) -> str | HandoffResult | None:
"""Execute download and return a payload path or completed external handoff."""
def post_process_cleanup(self, task: DownloadTask, *, success: bool) -> None:
"""Run optional cleanup after orchestrator post-processing.
@@ -394,6 +411,14 @@ class DownloadHandler(ABC):
"""Return private queue-time fields needed for restart-safe retry."""
return {}
def list_files(self, release_data: dict[str, Any]) -> list[PackFile] | None:
"""Return the release's file list without downloading it.
Lets the UI review a multi-book pack before queueing. Return None when the
source cannot know the files ahead of time (magnet links, usenet, ...).
"""
return None
@abstractmethod
def cancel(self, task_id: str) -> bool:
"""Cancel an in-progress download."""
@@ -407,6 +432,7 @@ _BUILTIN_SOURCE_MODULES = (
"shelfmark.release_sources.audiobookbay",
"shelfmark.release_sources.direct_download",
"shelfmark.release_sources.irc",
"shelfmark.release_sources.libgen",
"shelfmark.release_sources.newznab",
"shelfmark.release_sources.prowlarr",
)
@@ -504,6 +530,10 @@ def browse_record_to_book_metadata(
"""Convert a source-native browse record into generic book metadata."""
resolved_title = title_override or str(record.title or "").strip() or "Unknown title"
resolved_author = author_override or str(record.author or "").strip()
# `author_override` is the frontend's display string, `authors.join(', ')` - every
# contributor, translators included. The split below is the only place that knows the
# commas were joins rather than part of a name, so `search_author` is taken from it
# rather than from the joined text. See issue #1252.
authors = [part.strip() for part in resolved_author.split(",") if part.strip()]
publish_year = None
@@ -520,7 +550,7 @@ def browse_record_to_book_metadata(
provider_display_name=get_source_display_name(record.source),
title=resolved_title,
search_title=resolved_title,
search_author=resolved_author or None,
search_author=authors[0] if authors else None,
authors=authors,
cover_url=record.preview,
description=record.description,
@@ -1,6 +1,6 @@
"""AudiobookBay download handler - resolves magnet links and uses shared client lifecycle."""
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any
from urllib.parse import urlparse
from shelfmark.core.config import config
@@ -22,6 +22,7 @@ if TYPE_CHECKING:
from collections.abc import Callable
from shelfmark.core.models import DownloadTask
from shelfmark.download.postprocess.packs import PackFile
logger = setup_logger(__name__)
DEFAULT_ABB_HOSTNAME = "audiobookbay.lu"
@@ -68,6 +69,19 @@ class AudiobookBayHandler(ExternalClientHandler):
return task_id
return None
def list_files(self, release_data: dict[str, Any]) -> list[PackFile] | None:
"""Read the torrent's file list off the detail page, without downloading."""
raw_url = release_data.get("download_url") or release_data.get("source_url")
detail_url = raw_url.strip() if isinstance(raw_url, str) else ""
hostname = _resolve_allowed_detail_hostname()
if not detail_url or not _detail_url_matches_host(detail_url, hostname):
logger.debug("Cannot list files for AudiobookBay release without a valid detail URL")
return None
detail_html = scraper.fetch_detail_html(detail_url, hostname)
if not detail_html:
return None
return scraper.extract_file_list(detail_html)
def _get_client(self, protocol: str) -> DownloadClient | None:
"""Compatibility shim so module-level patching still works in tests."""
return get_client(protocol)
+116 -32
View File
@@ -2,7 +2,8 @@
import re
import time
from urllib.parse import quote
from threading import Lock
from urllib.parse import quote, quote_plus
import requests
from bs4 import BeautifulSoup
@@ -10,6 +11,8 @@ from bs4 import BeautifulSoup
from shelfmark.core.config import config
from shelfmark.core.logger import setup_logger
from shelfmark.download import http as downloader
from shelfmark.download.postprocess.packs import PackFile
from shelfmark.release_sources.audiobookbay.utils import normalize_search_punctuation
logger = setup_logger(__name__)
@@ -31,6 +34,13 @@ FIRST_PAGE_SESSION_REFRESH_ATTEMPTS = 2
# Legacy search parameter used by older ABB flows
LEGACY_CATEGORY_QUERY = "undefined%2Cundefined"
# Detail pages are fetched once and shared by inspection (file list) and download
# (magnet link) so a "review then download" round trip costs ABB a single request.
DETAIL_PAGE_CACHE_TTL_SECONDS = 120.0
DETAIL_PAGE_CACHE_MAX_ENTRIES = 8
_detail_page_cache: dict[str, tuple[float, str]] = {}
_detail_page_cache_lock = Lock()
# Precompiled patterns used while parsing result cards
LANGUAGE_PATTERN = re.compile(r"Language:\s*([A-Za-z]+)")
POSTED_PATTERN = re.compile(r"Posted:\s*(\d+\s+[A-Za-z]+\s+\d{4})")
@@ -38,6 +48,11 @@ FORMAT_PATTERN = re.compile(r"Format:\s*([A-Za-z0-9]+)")
BITRATE_PATTERN = re.compile(r"Bitrate:\s*([\d]+\s*[A-Za-z/]+)")
SIZE_PATTERN = re.compile(r"File Size:\s*([\d.]+)\s*([A-Za-z]+)")
INFO_HASH_LABEL_PATTERN = re.compile(r"Info Hash", re.IGNORECASE)
FILE_ROW_SIZE_PATTERN = re.compile(
r"^(?P<name>.+?)\s+(?P<size>\d+(?:\.\d+)?)\s*(?P<unit>Bytes?|KBs?|MBs?|GBs?|TBs?)$",
re.IGNORECASE,
)
_FILE_SIZE_MULTIPLIERS = {"b": 1, "k": 1024, "m": 1024**2, "g": 1024**3, "t": 1024**4}
def _coerce_non_negative_float(value: object, default: float) -> float:
@@ -98,8 +113,10 @@ def _encode_search_query(query: str, *, exact_phrase: bool) -> str:
and not (search_query.startswith('"') and search_query.endswith('"'))
):
search_query = f'"{search_query}"'
# Keep ABB-friendly encoding style (spaces as '+') while percent-encoding quotes.
return search_query.replace('"', "%22").replace(" ", "+")
# Keep ABB's space-as-'+' style, but percent-encode everything else: a bare
# '&' would otherwise start a new query parameter, '%' would open an invalid
# escape, and a literal '+' would arrive as a space.
return quote_plus(search_query)
def _normalize_result_url(url: str, hostname: str) -> str:
@@ -153,6 +170,9 @@ def search_audiobookbay(
"""
results = []
# ABB matches the stored, untexturized title, so a curly apostrophe reaching
# the search returns nothing at all rather than merely ranking worse.
query = normalize_search_punctuation(query)
rate_limit_delay = _coerce_non_negative_float(config.get("ABB_RATE_LIMIT_DELAY", 1.0), 1.0)
session = requests.Session()
@@ -342,6 +362,98 @@ def search_audiobookbay(
return results
def _get_cached_detail_page(details_url: str) -> str | None:
with _detail_page_cache_lock:
entry = _detail_page_cache.get(details_url)
if entry is None:
return None
fetched_at, html = entry
if time.monotonic() - fetched_at > DETAIL_PAGE_CACHE_TTL_SECONDS:
del _detail_page_cache[details_url]
return None
return html
def _store_cached_detail_page(details_url: str, html: str) -> None:
with _detail_page_cache_lock:
_detail_page_cache[details_url] = (time.monotonic(), html)
while len(_detail_page_cache) > DETAIL_PAGE_CACHE_MAX_ENTRIES:
oldest = min(_detail_page_cache, key=lambda key: _detail_page_cache[key][0])
del _detail_page_cache[oldest]
def clear_detail_page_cache() -> None:
"""Drop cached detail pages (used by tests)."""
with _detail_page_cache_lock:
_detail_page_cache.clear()
def _fetch_detail_page_once(details_url: str, hostname: str) -> str:
session = requests.Session()
_bootstrap_abb_session(hostname, session, DETAIL_PAGE_RETRY_ATTEMPTS)
return _coerce_markup_to_html(
downloader.html_get_page(
details_url,
retry=DETAIL_PAGE_RETRY_ATTEMPTS,
use_bypasser=False,
allow_bypasser_fallback=False,
success_delay=0,
session=session,
)
)
def fetch_detail_html(details_url: str, hostname: str = "audiobookbay.lu") -> str:
"""Fetch a detail page (one retry with a fresh session), cached briefly per URL."""
cached = _get_cached_detail_page(details_url)
if cached is not None:
logger.debug("Reusing recently fetched detail page: %s", details_url)
return cached
detail_html = _fetch_detail_page_once(details_url, hostname)
if not detail_html:
detail_html = _fetch_detail_page_once(details_url, hostname)
if detail_html:
_store_cached_detail_page(details_url, detail_html)
return detail_html
def _parse_file_row(text: str) -> PackFile | None:
match = FILE_ROW_SIZE_PATTERN.match(text.strip())
if not match:
return None
multiplier = _FILE_SIZE_MULTIPLIERS[match.group("unit")[0].lower()]
return PackFile(match.group("name"), int(float(match.group("size")) * multiplier))
def extract_file_list(detail_html: str) -> list[PackFile] | None:
"""Read the torrent file rows off a detail page.
ABB renders the torrent's file table as single-cell rows between the
"This is a Multifile Torrent" marker (absent for single-file torrents) and the
"Combined File Size" row. Returns None when the page has no such table.
"""
soup = BeautifulSoup(detail_html, "html.parser")
rows: list[PackFile] = []
for row in soup.find_all("tr"):
cells = row.find_all("td")
if not cells:
continue
label = cells[0].get_text(" ", strip=True)
if label.lower().startswith("combined file size"):
return rows or None
if len(cells) != 1:
rows = [] # a two-column metadata row means we're not in the file table yet
continue
text = cells[0].get_text(" ", strip=True)
if "multifile torrent" in text.lower():
rows = []
continue
parsed = _parse_file_row(text)
if parsed is not None:
rows.append(parsed)
return None
def extract_magnet_link(details_url: str, hostname: str = "audiobookbay.lu") -> str | None:
"""Extract info hash and trackers from book detail page, then construct magnet link.
@@ -354,35 +466,7 @@ def extract_magnet_link(details_url: str, hostname: str = "audiobookbay.lu") ->
"""
try:
session = requests.Session()
_bootstrap_abb_session(hostname, session, DETAIL_PAGE_RETRY_ATTEMPTS)
# Fetch detail page
detail_html = _coerce_markup_to_html(
downloader.html_get_page(
details_url,
retry=DETAIL_PAGE_RETRY_ATTEMPTS,
use_bypasser=False,
allow_bypasser_fallback=False,
success_delay=0,
session=session,
)
)
if not detail_html:
session = requests.Session()
_bootstrap_abb_session(hostname, session, DETAIL_PAGE_RETRY_ATTEMPTS)
detail_html = _coerce_markup_to_html(
downloader.html_get_page(
details_url,
retry=DETAIL_PAGE_RETRY_ATTEMPTS,
use_bypasser=False,
allow_bypasser_fallback=False,
success_delay=0,
session=session,
)
)
detail_html = fetch_detail_html(details_url, hostname)
if not detail_html:
logger.warning("Failed to fetch details page")
return None
@@ -9,6 +9,7 @@ if TYPE_CHECKING:
from shelfmark.metadata_providers import BookMetadata
from shelfmark.core.config import config
from shelfmark.core.languages import normalize_language
from shelfmark.core.logger import setup_logger
from shelfmark.release_sources import (
ColumnAlign,
@@ -22,7 +23,11 @@ from shelfmark.release_sources import (
register_source,
)
from shelfmark.release_sources.audiobookbay import scraper
from shelfmark.release_sources.audiobookbay.utils import normalize_hostname, parse_size
from shelfmark.release_sources.audiobookbay.utils import (
normalize_hostname,
normalize_search_punctuation,
parse_size,
)
logger = setup_logger(__name__)
MIN_RELEVANCE_QUERY_WORD_LENGTH = 2
@@ -43,41 +48,6 @@ def _coerce_positive_int(value: object, default: int) -> int:
# Map language names to ISO 639-1 codes (matching frontend color maps)
LANGUAGE_MAP = {
"english": "en",
"spanish": "es",
"french": "fr",
"german": "de",
"italian": "it",
"portuguese": "pt",
"russian": "ru",
"japanese": "ja",
"chinese": "zh",
"dutch": "nl",
"swedish": "sv",
"norwegian": "no",
"danish": "da",
"finnish": "fi",
"polish": "pl",
"czech": "cs",
"hungarian": "hu",
"korean": "ko",
"arabic": "ar",
"hebrew": "he",
"turkish": "tr",
"greek": "el",
"hindi": "hi",
"thai": "th",
"vietnamese": "vi",
"indonesian": "id",
"ukrainian": "uk",
"romanian": "ro",
"bulgarian": "bg",
"catalan": "ca",
"croatian": "hr",
"slovenian": "sl",
"serbian": "sr",
}
def _split_title_and_author(raw_title: str) -> tuple[str, str | None]:
@@ -119,8 +89,9 @@ def _map_language(language: str) -> str | None:
if not language:
return None
lang_lower = language.lower().strip()
return LANGUAGE_MAP.get(lang_lower, lang_lower)
# Fall back to the raw value so an unrecognised language is still shown
# rather than silently dropped from the release row.
return normalize_language(language) or language.lower().strip()
def _parse_bitrate_to_kbps(bitrate: str | None) -> int | None:
@@ -260,10 +231,12 @@ class AudiobookBaySource(ReleaseSource):
deduped_queries[index + 1].lower(),
)
# Extract query words for relevance checking
# Extract query words for relevance checking. Both sides of the
# comparison are punctuation-normalized: scraped titles carry the
# typographic forms WordPress renders, queries carry the ASCII ones.
query_words = {
word.lower()
for word in query_lower.split()
for word in normalize_search_punctuation(query_lower).split()
if len(word) > MIN_RELEVANCE_QUERY_WORD_LENGTH
}
@@ -272,7 +245,7 @@ class AudiobookBaySource(ReleaseSource):
try:
raw_title = result["title"]
title, author = _split_title_and_author(raw_title)
title_for_filter = raw_title.lower()
title_for_filter = normalize_search_punctuation(raw_title).lower()
# Basic relevance check: ensure title contains at least one query word
# This filters out homepage "Latest" feed items that may leak through
@@ -2,6 +2,63 @@
import re
# WordPress texturizes punctuation on output only: a post stored as "The
# Stranger's Wife" is rendered as "The Stranger’s Wife". ABB's search matches the
# stored value, so a query carrying the typographic form matches nothing -- and
# because ABB ANDs its search terms, one such term empties the entire result set.
# Book metadata and phone keyboards both hand us the typographic forms, so map
# them back before they reach a search or a title comparison.
_ASCII_PUNCTUATION = str.maketrans(
{
# Single quotes
"‘": "'", # left single quotation mark
"’": "'", # right single quotation mark
"‚": "'", # single low-9 quotation mark
"‛": "'", # single high-reversed-9 quotation mark
"′": "'", # prime
"´": "'", # acute accent
"`": "'", # grave accent
# Double quotes
"“": '"', # left double quotation mark
"”": '"', # right double quotation mark
"„": '"', # double low-9 quotation mark
"‟": '"', # double high-reversed-9 quotation mark
"″": '"', # double prime
# Dashes
"‐": "-", # hyphen
"‑": "-", # non-breaking hyphen
"‒": "-", # figure dash
"–": "-", # en dash
"—": "-", # em dash
"―": "-", # horizontal bar
"−": "-", # minus sign
"﹘": "-", # small em dash
"﹣": "-", # small hyphen-minus
"-": "-", # fullwidth hyphen-minus
# Ellipsis
"…": "...", # horizontal ellipsis
}
)
def normalize_search_punctuation(text: str) -> str:
"""Replace typographic punctuation with the ASCII forms ABB stores.
Each character is mapped individually rather than collapsing runs, so an
ASCII "--" is left alone: only characters ABB cannot have stored are
rewritten.
Args:
text: A search query, or a scraped title being compared against one.
Returns:
The text with curly quotes, dashes and ellipses mapped to ASCII.
"""
if not text:
return text
return text.translate(_ASCII_PUNCTUATION)
def normalize_hostname(raw: str | None) -> str:
"""Normalize a user-supplied hostname for URL construction.

Some files were not shown because too many files have changed in this diff Show More