Compare commits

...
Author SHA1 Message Date
Anthony Stirling a591a0b851 Merge remote-tracking branch 'origin/main' into fix/sanitize-empty-aa-dictionary 2026-08-21 08:43:24 +01:00
Anthony Stirling 5f9c396fdd test: remove PdfUaBenchmarkTest (#7613) 2026-08-21 08:41:58 +01:00
Anthony Stirling 0558ee51d7 fix(sanitize): stop adding an empty /AA dictionary to the catalog 2026-08-21 07:51:13 +01:00
stirlingbot[bot] e092b487ec Update Backend 3rd Party Licenses (#7608)
Auto-generated by stirlingbot[bot]

This PR updates the backend license report based on dependency changes.

Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2026-08-20 21:38:26 +00:00
dependabot[bot] a7c6fa6ef6 build(deps): bump io.swagger.core.v3:swagger-core-jakarta from 2.2.46 to 2.2.53 (#7526)
Bumps io.swagger.core.v3:swagger-core-jakarta from 2.2.46 to 2.2.53.


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=io.swagger.core.v3:swagger-core-jakarta&package-manager=gradle&previous-version=2.2.46&new-version=2.2.53)](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-20 21:29:49 +00:00
brios 4e5b1102b6 refactor(search): improve TextInput clear button styling and add component unit tests (#7578)
# Description of Changes

FIxes minor stylistic problem about the search bar. Mainly the X and the
spacing on the result's icons vs text.


### New
<img width="1594" height="600" alt="image"
src="https://github.com/user-attachments/assets/f3710cad-f1a6-4aa1-9c2a-f3f474fd2dd6"
/>


### Old
<img width="1584" height="618" alt="image"
src="https://github.com/user-attachments/assets/2d9c26a8-2d68-4d86-9d51-b12f90b17fe0"
/>


<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [X] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [X] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [X] I have performed a self-review of my own code
- [X] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [X] I have run `task check` to verify linters, typechecks, and tests
pass
- [X] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 21:21:50 +00:00
dependabot[bot] b3acdc4aab build(deps): bump actions/setup-java from 5.2.0 to 5.7.0 (#7530)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from
5.2.0 to 5.7.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/setup-java/releases">actions/setup-java's
releases</a>.</em></p>
<blockquote>
<h2>v5.7.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix npm audit failures on releases/v5 by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1154">actions/setup-java#1154</a></li>
<li>Backport <a
href="https://redirect.github.com/actions/setup-java/issues/1151">#1151</a>:
Fix missing wrapper cache distributions by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1153">actions/setup-java#1153</a></li>
<li>Deprecate legacy Adopt distributions in v5 by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1186">actions/setup-java#1186</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-java/compare/v5.6.0...v5.7.0">https://github.com/actions/setup-java/compare/v5.6.0...v5.7.0</a></p>
<h2>v5.6.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Backport to v5: Add Maven compiler problem matcher for javac
diagnostics by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1087">actions/setup-java#1087</a></li>
<li>feat: expose cache-primary-key output (<a
href="https://redirect.github.com/actions/setup-java/issues/597">#597</a>)
[v5 backport] by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1089">actions/setup-java#1089</a></li>
<li>dist: Cover Tencent Kona JDK 25 (<a
href="https://redirect.github.com/actions/setup-java/issues/1108">#1108</a>)
[v5 backport] by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1110">actions/setup-java#1110</a></li>
<li>Backport <a
href="https://redirect.github.com/actions/setup-java/issues/1111">#1111</a>:
Preserve Maven toolchains across repeated setup-java runs (<a
href="https://redirect.github.com/actions/setup-java/issues/1099">#1099</a>)
by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1113">actions/setup-java#1113</a></li>
<li>Backport <a
href="https://redirect.github.com/actions/setup-java/issues/1097">#1097</a>/<a
href="https://redirect.github.com/actions/setup-java/issues/1098">#1098</a>
to v5: cache Maven and Gradle wrapper distributions separately by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1122">actions/setup-java#1122</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-java/compare/v5...v5.6.0">https://github.com/actions/setup-java/compare/v5...v5.6.0</a></p>
<h2>v5.5.0</h2>
<h2>What's Changed</h2>
<ul>
<li>chore: enforce pre-PR validation (aggregate scripts, git hooks, PR
checklist) by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1061">actions/setup-java#1061</a></li>
<li>Bump github/codeql-action from 3 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-java/pull/1069">actions/setup-java#1069</a></li>
<li>Bump actions/checkout from 6 to 7 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-java/pull/1068">actions/setup-java#1068</a></li>
<li>Bump actions/setup-python from 5 to 6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-java/pull/1067">actions/setup-java#1067</a></li>
<li>Bump <code>@​typescript-eslint/parser</code> from 8.61.1 to 8.62.0
by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-java/pull/1062">actions/setup-java#1062</a></li>
<li>feat: Add verify-signature plumbing and Temurin+Microsoft
verification support by <a
href="https://github.com/johnoliver"><code>@​johnoliver</code></a> in <a
href="https://redirect.github.com/actions/setup-java/pull/1060">actions/setup-java#1060</a></li>
<li>Updated jetbrains test: https.request() now catches errors. This
fixes leaking tests as well by <a
href="https://github.com/jmjaffe37"><code>@​jmjaffe37</code></a> in <a
href="https://redirect.github.com/actions/setup-java/pull/1070">actions/setup-java#1070</a></li>
<li>Fix arm64 e2e workflow tests mislabeled as x64 by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a>
with <a href="https://github.com/Copilot"><code>@​Copilot</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1073">actions/setup-java#1073</a></li>
<li>feat: suppress Maven transfer progress via MAVEN_ARGS by default
(add show-download-progress input) by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1053">actions/setup-java#1053</a></li>
<li>feat: Disable interactiveMode in generated Maven settings.xml by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a>
with <a href="https://github.com/Copilot"><code>@​Copilot</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1052">actions/setup-java#1052</a></li>
<li>Bump prettier from 3.6.2 to 3.9.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-java/pull/1066">actions/setup-java#1066</a></li>
<li>chore(deps-dev): bump eslint-plugin-jest from 29.0.1 to 29.15.4 by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-java/pull/1074">actions/setup-java#1074</a></li>
<li>fix: Maven Toolchains grows unexpectedly by <a
href="https://github.com/Okeanos"><code>@​Okeanos</code></a> in <a
href="https://redirect.github.com/actions/setup-java/pull/534">actions/setup-java#534</a></li>
<li>dist: Support Tencent Kona JDK by <a
href="https://github.com/johnshajiang"><code>@​johnshajiang</code></a>
in <a
href="https://redirect.github.com/actions/setup-java/pull/672">actions/setup-java#672</a></li>
<li>feat: Add set-default option by <a
href="https://github.com/gsmet"><code>@​gsmet</code></a> in <a
href="https://redirect.github.com/actions/setup-java/pull/1017">actions/setup-java#1017</a></li>
<li>docs: document problem matcher (and how to disable it), Maven
Wrapper caching, and generated interactiveMode by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1075">actions/setup-java#1075</a></li>
<li>feat: Add distribution detection support to .sdkmanrc file by <a
href="https://github.com/lukaszgyg"><code>@​lukaszgyg</code></a> in <a
href="https://redirect.github.com/actions/setup-java/pull/975">actions/setup-java#975</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/jmjaffe37"><code>@​jmjaffe37</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-java/pull/1070">actions/setup-java#1070</a></li>
<li><a href="https://github.com/gsmet"><code>@​gsmet</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/setup-java/pull/1017">actions/setup-java#1017</a></li>
<li><a href="https://github.com/lukaszgyg"><code>@​lukaszgyg</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-java/pull/975">actions/setup-java#975</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-java/compare/v5...v5.5.0">https://github.com/actions/setup-java/compare/v5...v5.5.0</a></p>
<h2>v5.4.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Bump <code>@​typescript-eslint/parser</code> from 8.48.0 to 8.61.1
by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-java/pull/1021">actions/setup-java#1021</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/setup-java/commit/b6effb05e454b25005698d916606bdc6ffcbf961"><code>b6effb0</code></a>
Deprecate legacy Adopt distributions in v5 (<a
href="https://redirect.github.com/actions/setup-java/issues/1186">#1186</a>)</li>
<li><a
href="https://github.com/actions/setup-java/commit/e498d2a66a953492f322542257b22125c989b422"><code>e498d2a</code></a>
Backport <a
href="https://redirect.github.com/actions/setup-java/issues/1151">#1151</a>:
Fix missing wrapper cache distributions (<a
href="https://redirect.github.com/actions/setup-java/issues/1153">#1153</a>)</li>
<li><a
href="https://github.com/actions/setup-java/commit/6a3384db745932178632d0e22b2bd28cad1678e6"><code>6a3384d</code></a>
Fix npm audit failures on releases/v5 (<a
href="https://redirect.github.com/actions/setup-java/issues/1154">#1154</a>)</li>
<li><a
href="https://github.com/actions/setup-java/commit/03ad4de0992f5dab5e18fcb136590ce7c4a0ac95"><code>03ad4de</code></a>
Backport <a
href="https://redirect.github.com/actions/setup-java/issues/1097">#1097</a>/<a
href="https://redirect.github.com/actions/setup-java/issues/1098">#1098</a>:
cache Maven and Gradle wrapper distributions separately...</li>
<li><a
href="https://github.com/actions/setup-java/commit/d229d2e858d9137cc0b3f118fa5184b9f0a44ac4"><code>d229d2e</code></a>
Backport <a
href="https://redirect.github.com/actions/setup-java/issues/1111">#1111</a>:
Preserve Maven toolchains across repeated setup-java runs (<a
href="https://redirect.github.com/actions/setup-java/issues/1">#1</a>...</li>
<li><a
href="https://github.com/actions/setup-java/commit/bbf0f6967066506f72571a96d5d6c67ca42ab460"><code>bbf0f69</code></a>
dist: Cover Tencent Kona JDK 25 (<a
href="https://redirect.github.com/actions/setup-java/issues/1110">#1110</a>)</li>
<li><a
href="https://github.com/actions/setup-java/commit/513edc4f8710565e4ad696f3b7d8e3bda584a46c"><code>513edc4</code></a>
feat: expose cache-primary-key output (<a
href="https://redirect.github.com/actions/setup-java/issues/597">#597</a>)
[v5 backport] (<a
href="https://redirect.github.com/actions/setup-java/issues/1089">#1089</a>)</li>
<li><a
href="https://github.com/actions/setup-java/commit/62df799a9c6e3022bb466697c66c36e9a2dbf347"><code>62df799</code></a>
Add Maven compiler problem matcher for javac diagnostics (<a
href="https://redirect.github.com/actions/setup-java/issues/1087">#1087</a>)</li>
<li><a
href="https://github.com/actions/setup-java/commit/176156a187714aaf460b0a3c8f21e8b4f784b978"><code>176156a</code></a>
chore: bump version to 5.6.0 for v5 release line</li>
<li><a
href="https://github.com/actions/setup-java/commit/bf7b8deac240b9cee05eb15ccdb1d2f424a54b9f"><code>bf7b8de</code></a>
build: rebuild dist for backported changes (<a
href="https://redirect.github.com/actions/setup-java/issues/1079">#1079</a>,
<a
href="https://redirect.github.com/actions/setup-java/issues/1083">#1083</a>,
<a
href="https://redirect.github.com/actions/setup-java/issues/1084">#1084</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/setup-java/compare/v5.2.0...b6effb05e454b25005698d916606bdc6ffcbf961">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-20 21:10:12 +00:00
dependabot[bot] b549964529 build(deps-dev): bump fpdf2 from 2.8.7 to 2.8.8 in /engine (#7560)
Bumps [fpdf2](https://github.com/py-pdf/fpdf2) from 2.8.7 to 2.8.8.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/py-pdf/fpdf2/releases">fpdf2's
releases</a>.</em></p>
<blockquote>
<h2>Add resource access controls, SVG complexity limits, Optional
Content Groups, and other features and bug fixes</h2>
<h2>[2.8.8] - 2026-08-09</h2>
<blockquote>
<p>[!WARNING]
Upgrading to <strong>fpdf2 2.8.8</strong> is strongly recommended for
applications that render user-provided images, SVGs, or HTML
content.</p>
</blockquote>
<h3>Added</h3>
<ul>
<li>Punjabi (pa) tutorial translation - thanks to <a
href="https://github.com/Pawansingh3889"><code>@​Pawansingh3889</code></a></li>
<li><code>FPDF.svg_limits</code> and <code>SVGLimits</code> to configure
SVG complexity limits while rendering SVG images</li>
<li><code>resource_access_policy</code> and <a
href="https://py-pdf.github.io/fpdf2/Security.html">Security
considerations</a> documentation</li>
<li><a
href="https://py-pdf.github.io/fpdf2/OptionalContent.html"><code>FPDF.optional_content()</code></a>
context manager to mark content as visible on screen only or in print
only, using PDF Optional Content Groups - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/441">#441</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/441">py-pdf/fpdf2#441</a>),
based on a recipe by <a
href="https://github.com/digidigital"><code>@​digidigital</code></a> -
thanks to <a
href="https://github.com/eugen-goebel"><code>@​eugen-goebel</code></a></li>
<li>basic support for SVG <code>&lt;symbol&gt;</code> elements in the
SVG parser - thanks to <a
href="https://github.com/Theo1335"><code>@​Theo1335</code></a></li>
<li>basic support for SVG <code>&lt;switch&gt;</code> elements in the
SVG parser - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/537">#537</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/537">py-pdf/fpdf2#537</a>)
- thanks to <a
href="https://github.com/dannymaaz"><code>@​dannymaaz</code></a></li>
<li>support for keeping aspect ratio for images in templates -
<em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1118">#1118</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1118">py-pdf/fpdf2#1118</a>)
- thanks to <a
href="https://github.com/prateek-dagar"><code>@​prateek-dagar</code></a></li>
</ul>
<h3>Fixed</h3>
<ul>
<li>custom height passed to <code>Paragraph.ln()</code> in a text region
is now applied to the line it terminates instead of the first line of
the following paragraph - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1786">#1786</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1786">py-pdf/fpdf2#1786</a>)
- thanks to <a
href="https://github.com/Sanjays2402"><code>@​Sanjays2402</code></a></li>
<li>the optional <code>numpy</code> import in
<code>image_parsing.py</code> no longer crashes on CPUs unsupported by
numpy's <code>manylinux</code> wheel baseline; <code>RuntimeError</code>
is now treated the same as <code>ImportError</code>, so
<code>numpy</code> degrades to unavailable instead of taking down
<code>import fpdf</code> - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1908">#1908</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1908">py-pdf/fpdf2#1908</a>)
- thanks to <a
href="https://github.com/stumpylog"><code>@​stumpylog</code></a></li>
<li>font state (family, style, size, current font, and the page-level
&quot;font is set&quot; flag) no longer leaks back onto the
<code>FPDF</code> instance after a <code>text_columns()</code> /
<code>text_region()</code> context exits, so a subsequent
<code>pdf.cell()</code> / <code>pdf.write()</code> renders at the
caller's font instead of the last paragraph's - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1804">#1804</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1804">py-pdf/fpdf2#1804</a>)
- thanks to <a
href="https://github.com/Pawansingh3889"><code>@​Pawansingh3889</code></a></li>
<li>text rendering when the first text on a page starts with a fallback
glyph - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1772">#1772</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1772">py-pdf/fpdf2#1772</a>)</li>
<li>preserve boundary-neutral formatting during bidirectional text
preprocessing - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1779">#1779</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1779">py-pdf/fpdf2#1779</a>)</li>
<li>transform application on user space gradients - <em>cf.</em> [issue
<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1784">#1784</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1784">py-pdf/fpdf2#1784</a>)</li>
<li>dependency extras for camelot-py and endesive on pyproject.toml -
<em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1792">#1792</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1792">py-pdf/fpdf2#1792</a>)</li>
<li>preserve link annotations during dry-run of
<code>FPDF.multi_cell</code> - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1807">#1807</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1807">py-pdf/fpdf2#1807</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>preserve two consecutive markdown links (without space inbetween) -
<em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1814">#1814</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1814">py-pdf/fpdf2#1814</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>support markdown style around markdown links - <em>cf.</em> [issue
<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1826">#1826</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1826">py-pdf/fpdf2#1826</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>Reset gstate for ToC-rendering - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1837">#1837</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1837">py-pdf/fpdf2#1837</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>preserve markdown format in <code>FPDF.multi_cell</code> in dry-run
- <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1840">#1840</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1840">py-pdf/fpdf2#1840</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>fix page order after dry-run of <code>FPDF.multi_cell</code> in ToC
- <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1836">#1836</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1836">py-pdf/fpdf2#1836</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>rendering SVG arcs with very small sweeps that previously rounded to
zero - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1831">#1831</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1831">py-pdf/fpdf2#1831</a>)</li>
<li>spurious &quot;Not enough horizontal space to render a single
character&quot; error when text without break opportunities is split
into many small fragments, e.g. by a fallback font alternating with the
main font - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1250">#1250</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1250">py-pdf/fpdf2#1250</a>)
- thanks to <a
href="https://github.com/uttam12331"><code>@​uttam12331</code></a></li>
<li>number of surviving escape characters - <strong>cf.</strong> [issue
<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1215">#1215</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1215">py-pdf/fpdf2#1215</a>)
- thanks to <a
href="https://github.com/amidou-naba"><code>@​amidou-naba</code></a></li>
<li>leading spaces on new lines inside <code>&lt;pre&gt;</code> and
<code>&lt;pre&gt;&lt;code&gt;</code> blocks are no longer dropped -
<em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1063">#1063</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1063">py-pdf/fpdf2#1063</a>)
- thanks to <a
href="https://github.com/eugen-goebel"><code>@​eugen-goebel</code></a></li>
<li><code>FPDF.set_font()</code> can restore <code>current_font</code>
when the selected font state diverged - <em>cf.</em> [PR <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1872">#1872</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/pull/1872">py-pdf/fpdf2#1872</a>)
- thanks to <a
href="https://github.com/gaoflow"><code>@​gaoflow</code></a></li>
<li>embed CID-keyed CFF fonts as raw CFF programs so browser PDF viewers
render them correctly - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1874">#1874</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1874">py-pdf/fpdf2#1874</a>)</li>
<li>fixed broken links on documentation not directly leading to the API
reference - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1876">#1876</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1876">py-pdf/fpdf2#1876</a>)
- thanks to <a
href="https://github.com/iamfazakb"><code>@​iamfazakb</code></a></li>
<li>reject SVG <code>&lt;use&gt;</code> cycles and excessive nested
expansion to prevent resource exhaustion in
<code>FPDF.image()</code></li>
<li>count SVG <code>&lt;switch&gt;</code> elements in SVG complexity
limits</li>
<li>declare the default base state and display order for Optional
Content Groups so PDF viewers can list layers correctly - <em>cf.</em>
[issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1895">#1895</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1895">py-pdf/fpdf2#1895</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>skip byte-for-byte compressed data comparison when zlib-ng is
detected, regardless of OS</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/py-pdf/fpdf2/blob/master/CHANGELOG.md">fpdf2's
changelog</a>.</em></p>
<blockquote>
<h2>[2.8.8] - 2026-08-09</h2>
<h3>Added</h3>
<ul>
<li>Punjabi (pa) tutorial translation - thanks to <a
href="https://github.com/Pawansingh3889"><code>@​Pawansingh3889</code></a></li>
<li><code>FPDF.svg_limits</code> and <code>SVGLimits</code> to configure
SVG complexity limits while rendering SVG images</li>
<li><code>resource_access_policy</code> and <a
href="https://py-pdf.github.io/fpdf2/Security.html">Security
considerations</a> documentation</li>
<li><a
href="https://py-pdf.github.io/fpdf2/OptionalContent.html"><code>FPDF.optional_content()</code></a>
context manager to mark content as visible on screen only or in print
only, using PDF Optional Content Groups - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/441">#441</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/441">py-pdf/fpdf2#441</a>),
based on a recipe by <a
href="https://github.com/digidigital"><code>@​digidigital</code></a> -
thanks to <a
href="https://github.com/eugen-goebel"><code>@​eugen-goebel</code></a></li>
<li>basic support for SVG <code>&lt;symbol&gt;</code> elements in the
SVG parser - thanks to <a
href="https://github.com/Theo1335"><code>@​Theo1335</code></a></li>
<li>basic support for SVG <code>&lt;switch&gt;</code> elements in the
SVG parser - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/537">#537</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/537">py-pdf/fpdf2#537</a>)
- thanks to <a
href="https://github.com/dannymaaz"><code>@​dannymaaz</code></a></li>
<li>support for keeping aspect ratio for images in templates -
<em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1118">#1118</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1118">py-pdf/fpdf2#1118</a>)
- thanks to <a
href="https://github.com/prateek-dagar"><code>@​prateek-dagar</code></a></li>
</ul>
<h3>Fixed</h3>
<ul>
<li>custom height passed to <code>Paragraph.ln()</code> in a text region
is now applied to the line it terminates instead of the first line of
the following paragraph - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1786">#1786</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1786">py-pdf/fpdf2#1786</a>)
- thanks to <a
href="https://github.com/Sanjays2402"><code>@​Sanjays2402</code></a></li>
<li>the optional <code>numpy</code> import in
<code>image_parsing.py</code> no longer crashes on CPUs unsupported by
numpy's <code>manylinux</code> wheel baseline; <code>RuntimeError</code>
is now treated the same as <code>ImportError</code>, so
<code>numpy</code> degrades to unavailable instead of taking down
<code>import fpdf</code> - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1908">#1908</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1908">py-pdf/fpdf2#1908</a>)
- thanks to <a
href="https://github.com/stumpylog"><code>@​stumpylog</code></a></li>
<li>font state (family, style, size, current font, and the page-level
&quot;font is set&quot; flag) no longer leaks back onto the
<code>FPDF</code> instance after a <code>text_columns()</code> /
<code>text_region()</code> context exits, so a subsequent
<code>pdf.cell()</code> / <code>pdf.write()</code> renders at the
caller's font instead of the last paragraph's - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1804">#1804</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1804">py-pdf/fpdf2#1804</a>)
- thanks to <a
href="https://github.com/Pawansingh3889"><code>@​Pawansingh3889</code></a></li>
<li>text rendering when the first text on a page starts with a fallback
glyph - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1772">#1772</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1772">py-pdf/fpdf2#1772</a>)</li>
<li>preserve boundary-neutral formatting during bidirectional text
preprocessing - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1779">#1779</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1779">py-pdf/fpdf2#1779</a>)</li>
<li>transform application on user space gradients - <em>cf.</em> [issue
<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1784">#1784</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1784">py-pdf/fpdf2#1784</a>)</li>
<li>dependency extras for camelot-py and endesive on pyproject.toml -
<em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1792">#1792</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1792">py-pdf/fpdf2#1792</a>)</li>
<li>preserve link annotations during dry-run of
<code>FPDF.multi_cell</code> - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1807">#1807</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1807">py-pdf/fpdf2#1807</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>preserve two consecutive markdown links (without space inbetween) -
<em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1814">#1814</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1814">py-pdf/fpdf2#1814</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>support markdown style around markdown links - <em>cf.</em> [issue
<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1826">#1826</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1826">py-pdf/fpdf2#1826</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>Reset gstate for ToC-rendering - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1837">#1837</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1837">py-pdf/fpdf2#1837</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>preserve markdown format in <code>FPDF.multi_cell</code> in dry-run
- <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1840">#1840</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1840">py-pdf/fpdf2#1840</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>fix page order after dry-run of <code>FPDF.multi_cell</code> in ToC
- <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1836">#1836</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1836">py-pdf/fpdf2#1836</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>rendering SVG arcs with very small sweeps that previously rounded to
zero - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1831">#1831</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1831">py-pdf/fpdf2#1831</a>)</li>
<li>spurious &quot;Not enough horizontal space to render a single
character&quot; error when text without break opportunities is split
into many small fragments, e.g. by a fallback font alternating with the
main font - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1250">#1250</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1250">py-pdf/fpdf2#1250</a>)
- thanks to <a
href="https://github.com/uttam12331"><code>@​uttam12331</code></a></li>
<li>number of surviving escape characters - <strong>cf.</strong> [issue
<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1215">#1215</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1215">py-pdf/fpdf2#1215</a>)
- thanks to <a
href="https://github.com/amidou-naba"><code>@​amidou-naba</code></a></li>
<li>leading spaces on new lines inside <code>&lt;pre&gt;</code> and
<code>&lt;pre&gt;&lt;code&gt;</code> blocks are no longer dropped -
<em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1063">#1063</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1063">py-pdf/fpdf2#1063</a>)
- thanks to <a
href="https://github.com/eugen-goebel"><code>@​eugen-goebel</code></a></li>
<li><code>FPDF.set_font()</code> can restore <code>current_font</code>
when the selected font state diverged - <em>cf.</em> [PR <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1872">#1872</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/pull/1872">py-pdf/fpdf2#1872</a>)
- thanks to <a
href="https://github.com/gaoflow"><code>@​gaoflow</code></a></li>
<li>embed CID-keyed CFF fonts as raw CFF programs so browser PDF viewers
render them correctly - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1874">#1874</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1874">py-pdf/fpdf2#1874</a>)</li>
<li>fixed broken links on documentation not directly leading to the API
reference - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1876">#1876</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1876">py-pdf/fpdf2#1876</a>)
- thanks to <a
href="https://github.com/iamfazakb"><code>@​iamfazakb</code></a></li>
<li>reject SVG <code>&lt;use&gt;</code> cycles and excessive nested
expansion to prevent resource exhaustion in
<code>FPDF.image()</code></li>
<li>count SVG <code>&lt;switch&gt;</code> elements in SVG complexity
limits</li>
<li>declare the default base state and display order for Optional
Content Groups so PDF viewers can list layers correctly - <em>cf.</em>
[issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1895">#1895</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1895">py-pdf/fpdf2#1895</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>skip byte-for-byte compressed data comparison when zlib-ng is
detected, regardless of OS</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/py-pdf/fpdf2/commit/e1fc653d8fce65d2fb118755bda8581a9bf1e4d9"><code>e1fc653</code></a>
release v2.8.8 (<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1916">#1916</a>)</li>
<li><a
href="https://github.com/py-pdf/fpdf2/commit/42ec66442bf278b9768ca787fe601d3d334c62e4"><code>42ec664</code></a>
add Sanjays2402 as a contributor for code (<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1915">#1915</a>)</li>
<li><a
href="https://github.com/py-pdf/fpdf2/commit/af9d7dc11c36db49364c0096ad1edca5d569c676"><code>af9d7dc</code></a>
fix(text_region): apply Paragraph.ln(h) height to the line it terminates
(<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1904">#1904</a>)</li>
<li><a
href="https://github.com/py-pdf/fpdf2/commit/81951f837b46782df38f7c4d6f4c8f2644ac9b7e"><code>81951f8</code></a>
add stumpylog as a contributor for bug, and code (<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1913">#1913</a>)</li>
<li><a
href="https://github.com/py-pdf/fpdf2/commit/b30c38c8cfc804342f429fffb26cc44f34f20af3"><code>b30c38c</code></a>
Fix: catch RuntimeError alongside ImportError for the optional numpy
import (...</li>
<li><a
href="https://github.com/py-pdf/fpdf2/commit/63df0d0f4f085d3a014166d2d752d9c1e77305e4"><code>63df0d0</code></a>
Update github/codeql-action action to v4.37.6 (<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1910">#1910</a>)</li>
<li><a
href="https://github.com/py-pdf/fpdf2/commit/11a30e672cad2157b657642ed290cb27eb57e482"><code>11a30e6</code></a>
Update crate-ci/typos action to v1.49.0 (<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1911">#1911</a>)</li>
<li><a
href="https://github.com/py-pdf/fpdf2/commit/f65707b369a7ca7aaa6226a77eabf69b9903eac4"><code>f65707b</code></a>
Update step-security/harden-runner action to v2.20.1 (<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1912">#1912</a>)</li>
<li><a
href="https://github.com/py-pdf/fpdf2/commit/a7f7c8945139fb5a20d1993fcff6484b5c2267c7"><code>a7f7c89</code></a>
Update github/codeql-action action to v4.37.4 (<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1907">#1907</a>)</li>
<li><a
href="https://github.com/py-pdf/fpdf2/commit/fe9d8f52643c5dcb88370813f51c29410463e299"><code>fe9d8f5</code></a>
Update pypa/gh-action-pypi-publish action to v1.14.2 (<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1906">#1906</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/py-pdf/fpdf2/compare/2.8.7...2.8.8">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=fpdf2&package-manager=uv&previous-version=2.8.7&new-version=2.8.8)](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-20 21:10:05 +00:00
github-actions[bot]andLudy87 8e3ecb1fe4 chore: update Gradle to 9.7.1 (#7607)
Automated update of the Gradle wrapper and Gradle Docker build images.

Gradle version: `9.7.1`
Docker image: `gradle:9.7.1-jdk25`

Co-authored-by: Ludy87 <4592558+Ludy87@users.noreply.github.com>
2026-08-20 20:46:50 +00:00
Ludy cb0cafabd9 ci: Extract Gradle cache priming into a reusable workflow (#7572)
# Description of Changes

- What was changed
- Moved the `gradle-cache-prime` job from `build.yml` into a dedicated
reusable workflow.
  - Added `workflow_call` support for invocation from other workflows.
  - Added a `push` trigger for the `main` branch.
  - Updated `build.yml` to call the new reusable workflow.

- Why the change was made
- Keeps the shared Gradle cache warm after changes are pushed to `main`.
- Allows pull request builds to reuse the same cache and reduce
dependency resolution time.
  - Separates cache maintenance from the main build workflow.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 20:03:54 +00:00
stirlingbot[bot] 90d39aec0b 🌐 Sync Translations + Update README Progress Table (#7583)
### Description of Changes

This Pull Request was automatically generated to synchronize updates to
translation files and documentation. Below are the details of the
changes made:

#### **1. Synchronization of Translation Files**
- Updated translation files
(`frontend/editor/public/locales/*/translation.toml`) to reflect changes
in the reference file `en-US/translation.toml`.
- Ensured consistency and synchronization across all supported language
files.
- Highlighted any missing or incomplete translations.
- **Format**: TOML

#### **2. Update README.md**
- Generated the translation progress table in `README.md` using
`counter_translation_v3.py`.
- Added a summary of the current translation status for all supported
languages.
- Included up-to-date statistics on translation coverage.

#### **Why these changes are necessary**
- Keeps translation files aligned with the latest reference updates.
- Ensures the documentation reflects the current translation progress.

---

Auto-generated by [create-pull-request][1].

[1]: https://github.com/peter-evans/create-pull-request

Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2026-08-20 19:44:56 +00:00
Ludy 6376d1ff8b ci: share Docker base image across test-build matrix (#7584)
# Description of Changes

This PR decouples the Docker base-image preparation from the embedded
Docker image matrix builds in `.github/workflows/test-build-docker.yml`.

- Added a dedicated `prepare-base-image` job that runs once when a pull
request changes the Docker base image.
- Builds `stirling-pdf-base:pr-test` once for `linux/amd64` instead of
rebuilding the same image independently in every matrix job.
- Exports the prepared image with `docker save`, compresses it, and
uploads it as a short-lived GitHub Actions artifact.
- Added a dependency from `test-build-docker-images` to
`prepare-base-image`, while still allowing the matrix job to run when
base-image preparation is skipped.
- Each matrix entry downloads and loads the prepared Docker image when
`docker-base-changed` is enabled.
- Removed the previous per-matrix `Build base image locally` step.
- Keeps the prepared image available to the embedded Docker builds
through the local Docker daemon.

The change was made to eliminate redundant base-image builds across the
Docker test matrix. Previously, pull requests modifying `docker/base`
caused each matrix entry to build the identical base image independently
and in parallel. Preparing the image once reduces duplicated CI work,
improves consistency between matrix entries, and should reduce CI
resource usage and execution time for Docker-related pull requests.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 19:43:33 +00:00
dependabot[bot] b8cfde3a7a build(deps): bump imageioVersion from 3.13.1 to 3.14.0 (#7598)
Bumps `imageioVersion` from 3.13.1 to 3.14.0.
Updates `com.twelvemonkeys.imageio:imageio-batik` from 3.13.1 to 3.14.0

Updates `com.twelvemonkeys.imageio:imageio-bmp` from 3.13.1 to 3.14.0

Updates `com.twelvemonkeys.imageio:imageio-jpeg` from 3.13.1 to 3.14.0

Updates `com.twelvemonkeys.imageio:imageio-tiff` from 3.13.1 to 3.14.0

Updates `com.twelvemonkeys.imageio:imageio-webp` from 3.13.1 to 3.14.0

Updates `com.twelvemonkeys.imageio:imageio-psd` from 3.13.1 to 3.14.0


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-20 19:42:11 +00:00
dependabot[bot] 43f7db0234 build(deps): bump github/codeql-action/upload-sarif from 4.37.6 to 4.37.7 (#7591)
Bumps
[github/codeql-action/upload-sarif](https://github.com/github/codeql-action)
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/upload-sarif'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/upload-sarif'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 compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action/upload-sarif&package-manager=github_actions&previous-version=4.37.6&new-version=4.37.7)](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-20 19:32:53 +00:00
dependabot[bot] 1f337c06e4 build(deps-dev): bump uvicorn from 0.52.1 to 0.52.3 in /engine (#7600)
Bumps [uvicorn](https://github.com/Kludex/uvicorn) from 0.52.1 to
0.52.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/Kludex/uvicorn/releases">uvicorn's
releases</a>.</em></p>
<blockquote>
<h2>Version 0.52.3</h2>
<h3>Changed</h3>
<ul>
<li>Update <code>zttp</code> to 0.0.24 and use its combined receive
path, improving HTTP/1.1 request parsing performance (<a
href="https://redirect.github.com/Kludex/uvicorn/issues/3067">#3067</a>)</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/Kludex/uvicorn/compare/0.52.2...0.52.3">https://github.com/Kludex/uvicorn/compare/0.52.2...0.52.3</a></p>
<h2>Version 0.52.2</h2>
<h3>Fixed</h3>
<ul>
<li>Update <code>zttp</code> to 0.0.22, fixing bodyless request receives
and improving HTTP/1 request parsing performance (<a
href="https://redirect.github.com/Kludex/uvicorn/issues/3063">#3063</a>)</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/Kludex/uvicorn/compare/0.52.1...0.52.2">https://github.com/Kludex/uvicorn/compare/0.52.1...0.52.2</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/Kludex/uvicorn/blob/main/docs/release-notes.md">uvicorn's
changelog</a>.</em></p>
<blockquote>
<h2>0.52.3 (August 13, 2026)</h2>
<h3>Changed</h3>
<ul>
<li>Update <code>zttp</code> to 0.0.24 and use its combined receive
path, improving HTTP/1.1 request parsing performance (<a
href="https://redirect.github.com/Kludex/uvicorn/issues/3067">#3067</a>)</li>
</ul>
<h2>0.52.2 (August 13, 2026)</h2>
<h3>Fixed</h3>
<ul>
<li>Update <code>zttp</code> to 0.0.22, fixing bodyless request receives
and improving HTTP/1 request parsing performance (<a
href="https://redirect.github.com/Kludex/uvicorn/issues/3063">#3063</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/Kludex/uvicorn/commit/a68da608147c5f79d962352dce11de8f6e32d972"><code>a68da60</code></a>
Version 0.52.3 (<a
href="https://redirect.github.com/Kludex/uvicorn/issues/3068">#3068</a>)</li>
<li><a
href="https://github.com/Kludex/uvicorn/commit/6e3bb4c5c22531c16d341f2da49f2653307f412f"><code>6e3bb4c</code></a>
Use zttp 0.0.24 fast receive path (<a
href="https://redirect.github.com/Kludex/uvicorn/issues/3067">#3067</a>)</li>
<li><a
href="https://github.com/Kludex/uvicorn/commit/2a5ff183e02e6dce62f363009c85574bd5a23de2"><code>2a5ff18</code></a>
Version 0.52.2 (<a
href="https://redirect.github.com/Kludex/uvicorn/issues/3066">#3066</a>)</li>
<li><a
href="https://github.com/Kludex/uvicorn/commit/1920297df92b9cf5e6befb00327c1e34276db46f"><code>1920297</code></a>
Update zttp to 0.0.22 (<a
href="https://redirect.github.com/Kludex/uvicorn/issues/3063">#3063</a>)</li>
<li><a
href="https://github.com/Kludex/uvicorn/commit/5c560dae6a87f6109d958c6154277f1dc727f704"><code>5c560da</code></a>
chore(deps): bump pymdown-extensions from 11.0 to 11.0.1 (<a
href="https://redirect.github.com/Kludex/uvicorn/issues/3061">#3061</a>)</li>
<li>See full diff in <a
href="https://github.com/Kludex/uvicorn/compare/0.52.1...0.52.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=uvicorn&package-manager=uv&previous-version=0.52.1&new-version=0.52.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 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-20 19:32:41 +00:00
Vivek Maddheshiya fb0bffaa31 fix(billing): enforce en-US locale for billing money formatting (#7487) (#7498)
# Description of Changes

Fixes #7487

- **What was changed:** Explicitly specified the `'en-US'` locale in
`Intl.NumberFormat` and `toLocaleString` within `formatMinor` and
`formatMoneyMajor` in
`frontend/editor/src/proprietary/billing/format.ts`. Also added test
coverage for `formatMoneyMajor` in
`frontend/editor/src/portal/billing/sharedBillingFormat.test.ts`.
- **Why the change was made:** Previously, `formatMinor` used `new
Intl.NumberFormat(undefined, ...)`, which inherited the host/browser
environment locale. On non-US locales (e.g. German `de-DE`), this
produced comma-separated decimals (`$2,24`) instead of dot-separated
decimals (`$2.24`), breaking unit tests and leading to inconsistent
money formatting.
- **Any challenges encountered:** None.

Closes #7487

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [x] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [x] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [x] I have run `task check` to verify linters, typechecks, and tests
pass
- [x] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 19:27:58 +00:00
Ludy df74c46211 ci: clean up temporary files in GitHub Actions workflows (#7582)
# Description of Changes

This change adds cleanup steps to GitHub Actions workflows that create
temporary files or sensitive build resources.

Changes include:

- Removing temporary backend helper files, logs, and PID files.
- Cleaning up database migration temporary directories after failure
logs are uploaded.
- Removing locally generated deployment files and Storybook archives.
- Deleting temporary signing certificates, MSI extraction directories,
and Apple signing keychains.
- Ensuring cleanup runs even when earlier workflow steps fail.

The cleanup reduces temporary data retention on runners and ensures
sensitive signing material is removed after builds. No functional
application code was changed.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 18:57:03 +00:00
Ludy 4f2b1bb381 Cache Docker dependency downloads in embedded images (#7535)
# Description of Changes

Adds Docker BuildKit syntax and shared Gradle/npm cache mounts to the
fat and ultra-lite embedded Dockerfiles. This reduces repeated
dependency downloads during rebuilds and speeds up image builds without
changing runtime behavior.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 18:56:59 +00:00
Ludy a24fcb0489 chore(dependabot): refresh dependency update configuration (#7561)
# Description of Changes

- Added `/testing/compose/mcp-client-check` to the existing Docker
Compose dependency monitoring configuration so dependencies used by the
MCP client check environment are covered by Dependabot.
- Added a `tanstack` dependency group matching `@tanstack/*` packages,
allowing related TanStack updates to be reviewed together.
- Added a `typescript` dependency group covering both `typescript` and
`@typescript/*` packages.
- Removed the obsolete `pip` Dependabot configuration for
`/testing/cucumber`, as dependency management for this area has moved to
the current `uv`-based setup.
- Kept the existing Dependabot scheduling, cooldown, grouping, and
rebase strategy conventions unchanged where applicable.


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 18:33:06 +00:00
Ludy 17938ff5bc ci: automate Gradle and Docker image updates (#7464)
## Summary

- Add a scheduled GitHub Actions workflow that checks for the latest
stable Gradle release.
- Update the Gradle wrapper and pinned Gradle Docker build images
automatically.
- Open an automated pull request when an update is available.
- Update the developer prerequisites to Node.js 22+ and Gradle 9.0+.

## Details

The workflow runs every Monday at 03:00 UTC and can also be triggered
manually. It:

1. Resolves the latest stable Gradle version.
2. Finds the matching `gradle:<version>-jdk25` Docker image digest.
3. Updates the Gradle wrapper and Dockerfiles.
4. Verifies the resolved wrapper version and checks the resulting diff.
5. Creates or updates an automated dependency pull request.

The current wrapper and Docker image changes are included as the initial
update generated by this workflow.

## Testing

- Verified the generated changes with `git diff --check`.
- The workflow validates the wrapper version before opening the
automated pull request.
2026-08-20 19:32:54 +01:00
Ludy 73a78ab423 ci: gate backend builds and cache priming (#7405)
# Description of Changes

- Added a dedicated backend path filter for Java and Gradle-related
changes.
- Gated backend builds on backend-relevant file changes.
- Limited Gradle cache priming to project changes.
- Made Docker image tests resilient to skipped upstream jobs.
- Included the Gradle cache prime job in the final CI status check.
- Removed the unnecessary build dependency from the license check.


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 19:32:11 +01:00
stirlingbot[bot] 6d549c39dc Update Frontend 3rd Party Licenses (#7594)
Auto-generated by stirlingbot[bot]

This PR updates the frontend license report based on changes to
package.json dependencies.

Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2026-08-20 16:04:27 +00:00
dependabot[bot] 71af798da4 build(deps): bump the simple-java-mail group across 2 directories with 2 updates (#7585)
Bumps the simple-java-mail group with 1 update in the / directory:
[org.simplejavamail:simple-java-mail](https://github.com/bbottema/simple-java-mail).
Bumps the simple-java-mail group with 1 update in the /app/common
directory:
[org.simplejavamail:simple-java-mail](https://github.com/bbottema/simple-java-mail).

Updates `org.simplejavamail:simple-java-mail` from 9.2.0 to 9.3.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/bbottema/simple-java-mail/releases">org.simplejavamail:simple-java-mail's
releases</a>.</em></p>
<blockquote>
<h2>v9.3.1</h2>
<p>Simple Java Mail 9.3.1 is a Java 8-compatible build-tool maintenance
release.</p>
<h2>Changes</h2>
<ul>
<li>Updated Maven Antrun Plugin from 3.1.0 to 3.2.0 for the JPMS
consumer-compilation check.</li>
<li>Updated Maven Dependency Plugin from 3.8.1 to 3.11.0 for
construction of the JPMS module path.</li>
</ul>
<p>These changes affect project build tooling only. This release
contains no runtime-dependency changes, public API changes, or intended
mail-sending behavior changes. Java 8 remains the minimum supported
runtime.</p>
<p>The maintenance pull requests are <a
href="https://redirect.github.com/bbottema/simple-java-mail/pull/700">#700</a>
and <a
href="https://redirect.github.com/bbottema/simple-java-mail/pull/701">#701</a>.</p>
<h2>v9.3.0</h2>
<p>Simple Java Mail 9.3.0 exposes <code>batch-module</code> as a
supported standalone Jakarta Mail orchestration API.</p>
<ul>
<li><a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/698">#698</a>
adds <code>BatchTransportExecutor&lt;K&gt;</code> for applications that
create their own <code>Session</code> and <code>MimeMessage</code>
objects without adopting <code>EmailBuilder</code> or
<code>Mailer</code>. The main <code>simple-java-mail</code> facade is
not required.</li>
<li>Register Sessions by cluster key, then run cluster-selected or
exact-Session callbacks synchronously or submit them as
<code>CompletableFuture</code> work. Each callback receives the actually
selected <code>Session</code> and connected <code>Transport</code>.</li>
<li>The facade keeps raw leases private, releases connections after
successful callbacks, invalidates them after escaping failures, resolves
OAuth2 credentials from the selected Session, and provides deterministic
graceful or forced shutdown. Its default executor is module-owned; an
injected executor remains caller-owned.</li>
<li>The existing pooled <code>Mailer</code> path and the standalone
facade now share one transport engine. <code>smtp-connection-pool</code>
remains the only physical pool owner; do not stack batch/direct
orchestration over the Jakarta <code>smtppool</code> provider.</li>
<li>The supporting chain is updated to <code>smtp-connection-pool
4.0.1</code>, <code>clustered-object-pool 4.0.3</code>, and
<code>generic-object-pool 2.4.2</code>. The published JPMS names are
<code>org.simplejavamail.batch</code>,
<code>org.simplejavamail.smtpconnectionpool</code>,
<code>org.bbottema.clusteredobjectpool</code>, and
<code>org.bbottema.genericobjectpool</code>.</li>
</ul>
<p>See the <a
href="https://www.simplejavamail.org/smtp-connection-pooling.html">SMTP
connection pooling and batch orchestration guide</a> for the comparison
matrix, ownership rules, and complete examples.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/bbottema/simple-java-mail/blob/master/RELEASE.txt">org.simplejavamail:simple-java-mail's
changelog</a>.</em></p>
<blockquote>
<p><a
href="https://www.simplejavamail.org">https://www.simplejavamail.org</a></p>
<!-- raw HTML omitted -->
<p>v9.3.0 - v9.3.2</p>
<ul>
<li><strong>v9.3.2:</strong> <a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/702">#702</a>:
clarified the single-address contract of RecipientBuilder by renaming
its misleading implementation parameter and validation label; use
RecipientsBuilder for comma- or semicolon-delimited address lists.</li>
<li><strong>v9.3.1:</strong> Updated Maven Antrun Plugin from 3.1.0 to
3.2.0 (<a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/700">#700</a>)
and Maven Dependency Plugin from 3.8.1 to 3.11.0 (<a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/701">#701</a>),
retaining Java 8 compatibility.</li>
<li><strong>v9.3.0:</strong> <a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/698">#698</a>:
exposed batch-module as a supported standalone Jakarta Mail
orchestration API with clustered and exact-Session callbacks,
asynchronous submission, automatic lease release/invalidation, and
deterministic graceful or forced shutdown.</li>
<li><strong>v9.3.0:</strong> Updated smtp-connection-pool from 3.1.0 to
4.0.1 and migrated the existing Mailer integration to its explicit
SmtpTransportLease contract. The complete generic, clustered, SMTP, and
batch dependency chain now publishes stable JPMS automatic module
names.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/e44c4c983e48ec9372ff745ccb9ab01d731867d0"><code>e44c4c9</code></a>
released 9.3.1 [skip ci]</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/461e5f7c3b97d4882b21bca7ecb4843530184f32"><code>461e5f7</code></a>
docs(release): prepare 9.3.1 release notes</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/68728f3457f7b0e85cf66e758f41985b86c4a125"><code>68728f3</code></a>
build(deps-dev): bump org.apache.maven.plugins:maven-dependency-plugin
(<a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/701">#701</a>)</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/d491c334bea647a99538984d917cd6ffdcb3cbce"><code>d491c33</code></a>
build(deps-dev): bump org.apache.maven.plugins:maven-antrun-plugin (<a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/700">#700</a>)</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/b3111e232a23f9187c2b0c6b98a0bb1a895d9309"><code>b3111e2</code></a>
docs(website): publish pooling guidance update [skip ci]</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/cb3a9a133b91372d112c1b6a07c92e721f1a6f9e"><code>cb3a9a1</code></a>
docs(website): publish pooling guide follow-up [skip ci]</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/29f25e83c654d5dbadd4da321ce9511a4eceeeef"><code>29f25e8</code></a>
released 9.3.0 [skip ci]</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/b9c0cb8b58f4477e92fdd7ea395583863f875310"><code>b9c0cb8</code></a>
feat(batch): expose standalone transport orchestration</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/92549b27f44a0042ca908d22c25db58ccc1e1630"><code>92549b2</code></a>
merge(release): reconcile master with develop [skip ci]</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/b499d76ec28e0566b2000ec6418473da66e1398a"><code>b499d76</code></a>
docs(readme): rebuild developer landing page [skip ci]</li>
<li>Additional commits viewable in <a
href="https://github.com/bbottema/simple-java-mail/compare/9.2.0...9.3.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `org.simplejavamail:outlook-module` from 9.2.0 to 9.3.1

Updates `org.simplejavamail:simple-java-mail` from 9.2.0 to 9.3.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/bbottema/simple-java-mail/releases">org.simplejavamail:simple-java-mail's
releases</a>.</em></p>
<blockquote>
<h2>v9.3.1</h2>
<p>Simple Java Mail 9.3.1 is a Java 8-compatible build-tool maintenance
release.</p>
<h2>Changes</h2>
<ul>
<li>Updated Maven Antrun Plugin from 3.1.0 to 3.2.0 for the JPMS
consumer-compilation check.</li>
<li>Updated Maven Dependency Plugin from 3.8.1 to 3.11.0 for
construction of the JPMS module path.</li>
</ul>
<p>These changes affect project build tooling only. This release
contains no runtime-dependency changes, public API changes, or intended
mail-sending behavior changes. Java 8 remains the minimum supported
runtime.</p>
<p>The maintenance pull requests are <a
href="https://redirect.github.com/bbottema/simple-java-mail/pull/700">#700</a>
and <a
href="https://redirect.github.com/bbottema/simple-java-mail/pull/701">#701</a>.</p>
<h2>v9.3.0</h2>
<p>Simple Java Mail 9.3.0 exposes <code>batch-module</code> as a
supported standalone Jakarta Mail orchestration API.</p>
<ul>
<li><a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/698">#698</a>
adds <code>BatchTransportExecutor&lt;K&gt;</code> for applications that
create their own <code>Session</code> and <code>MimeMessage</code>
objects without adopting <code>EmailBuilder</code> or
<code>Mailer</code>. The main <code>simple-java-mail</code> facade is
not required.</li>
<li>Register Sessions by cluster key, then run cluster-selected or
exact-Session callbacks synchronously or submit them as
<code>CompletableFuture</code> work. Each callback receives the actually
selected <code>Session</code> and connected <code>Transport</code>.</li>
<li>The facade keeps raw leases private, releases connections after
successful callbacks, invalidates them after escaping failures, resolves
OAuth2 credentials from the selected Session, and provides deterministic
graceful or forced shutdown. Its default executor is module-owned; an
injected executor remains caller-owned.</li>
<li>The existing pooled <code>Mailer</code> path and the standalone
facade now share one transport engine. <code>smtp-connection-pool</code>
remains the only physical pool owner; do not stack batch/direct
orchestration over the Jakarta <code>smtppool</code> provider.</li>
<li>The supporting chain is updated to <code>smtp-connection-pool
4.0.1</code>, <code>clustered-object-pool 4.0.3</code>, and
<code>generic-object-pool 2.4.2</code>. The published JPMS names are
<code>org.simplejavamail.batch</code>,
<code>org.simplejavamail.smtpconnectionpool</code>,
<code>org.bbottema.clusteredobjectpool</code>, and
<code>org.bbottema.genericobjectpool</code>.</li>
</ul>
<p>See the <a
href="https://www.simplejavamail.org/smtp-connection-pooling.html">SMTP
connection pooling and batch orchestration guide</a> for the comparison
matrix, ownership rules, and complete examples.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/bbottema/simple-java-mail/blob/master/RELEASE.txt">org.simplejavamail:simple-java-mail's
changelog</a>.</em></p>
<blockquote>
<p><a
href="https://www.simplejavamail.org">https://www.simplejavamail.org</a></p>
<!-- raw HTML omitted -->
<p>v9.3.0 - v9.3.2</p>
<ul>
<li><strong>v9.3.2:</strong> <a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/702">#702</a>:
clarified the single-address contract of RecipientBuilder by renaming
its misleading implementation parameter and validation label; use
RecipientsBuilder for comma- or semicolon-delimited address lists.</li>
<li><strong>v9.3.1:</strong> Updated Maven Antrun Plugin from 3.1.0 to
3.2.0 (<a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/700">#700</a>)
and Maven Dependency Plugin from 3.8.1 to 3.11.0 (<a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/701">#701</a>),
retaining Java 8 compatibility.</li>
<li><strong>v9.3.0:</strong> <a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/698">#698</a>:
exposed batch-module as a supported standalone Jakarta Mail
orchestration API with clustered and exact-Session callbacks,
asynchronous submission, automatic lease release/invalidation, and
deterministic graceful or forced shutdown.</li>
<li><strong>v9.3.0:</strong> Updated smtp-connection-pool from 3.1.0 to
4.0.1 and migrated the existing Mailer integration to its explicit
SmtpTransportLease contract. The complete generic, clustered, SMTP, and
batch dependency chain now publishes stable JPMS automatic module
names.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/e44c4c983e48ec9372ff745ccb9ab01d731867d0"><code>e44c4c9</code></a>
released 9.3.1 [skip ci]</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/461e5f7c3b97d4882b21bca7ecb4843530184f32"><code>461e5f7</code></a>
docs(release): prepare 9.3.1 release notes</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/68728f3457f7b0e85cf66e758f41985b86c4a125"><code>68728f3</code></a>
build(deps-dev): bump org.apache.maven.plugins:maven-dependency-plugin
(<a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/701">#701</a>)</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/d491c334bea647a99538984d917cd6ffdcb3cbce"><code>d491c33</code></a>
build(deps-dev): bump org.apache.maven.plugins:maven-antrun-plugin (<a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/700">#700</a>)</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/b3111e232a23f9187c2b0c6b98a0bb1a895d9309"><code>b3111e2</code></a>
docs(website): publish pooling guidance update [skip ci]</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/cb3a9a133b91372d112c1b6a07c92e721f1a6f9e"><code>cb3a9a1</code></a>
docs(website): publish pooling guide follow-up [skip ci]</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/29f25e83c654d5dbadd4da321ce9511a4eceeeef"><code>29f25e8</code></a>
released 9.3.0 [skip ci]</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/b9c0cb8b58f4477e92fdd7ea395583863f875310"><code>b9c0cb8</code></a>
feat(batch): expose standalone transport orchestration</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/92549b27f44a0042ca908d22c25db58ccc1e1630"><code>92549b2</code></a>
merge(release): reconcile master with develop [skip ci]</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/b499d76ec28e0566b2000ec6418473da66e1398a"><code>b499d76</code></a>
docs(readme): rebuild developer landing page [skip ci]</li>
<li>Additional commits viewable in <a
href="https://github.com/bbottema/simple-java-mail/compare/9.2.0...9.3.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `org.simplejavamail:outlook-module` from 9.2.0 to 9.3.1


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 15:00:25 +00:00
dependabot[bot] d9a5c9ac7e build(deps): bump the embedpdf group across 1 directory with 23 updates (#7469)
Bumps the embedpdf group with 21 updates in the /frontend directory:

| Package | From | To |
| --- | --- | --- |
|
[@embedpdf/core](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/core/main)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/models](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/models)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-annotation](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-annotation)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-attachment](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-attachment)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-bookmark](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-bookmark)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-document-manager](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-document-manager)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-export](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-download)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-history](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-history)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-interaction-manager](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-interaction-manager)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-pan](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-pan)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-print](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-print)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-redaction](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-redaction)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-render](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-render)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-rotate](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-rotate)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-scroll](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-scroll)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-search](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-search)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-spread](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-spread)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-thumbnail](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-thumbnail)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-tiling](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-tiling)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-viewport](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-viewport)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-zoom](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-zoom)
| `2.14.4` | `2.15.0` |


Updates `@embedpdf/core` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/core's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/core/main">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/engines` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/engines's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/engines/CHANGELOG.md">@​embedpdf/engines's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/engines">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/models` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/models's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/models/CHANGELOG.md">@​embedpdf/models's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/models">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-annotation` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-annotation's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-annotation/CHANGELOG.md">@​embedpdf/plugin-annotation's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-annotation">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-attachment` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-attachment's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-attachment/CHANGELOG.md">@​embedpdf/plugin-attachment's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-attachment">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-bookmark` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-bookmark's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-bookmark/CHANGELOG.md">@​embedpdf/plugin-bookmark's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-bookmark">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-document-manager` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-document-manager's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-document-manager/CHANGELOG.md">@​embedpdf/plugin-document-manager's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-document-manager">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-export` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-export's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-download">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-history` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-history's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-history/CHANGELOG.md">@​embedpdf/plugin-history's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-history">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-interaction-manager` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-interaction-manager's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-interaction-manager/CHANGELOG.md">@​embedpdf/plugin-interaction-manager's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-interaction-manager">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-pan` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-pan's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-pan/CHANGELOG.md">@​embedpdf/plugin-pan's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-pan">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-print` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-print's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-print/CHANGELOG.md">@​embedpdf/plugin-print's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-print">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-redaction` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-redaction's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-redaction/CHANGELOG.md">@​embedpdf/plugin-redaction's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-redaction">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-render` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-render's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-render/CHANGELOG.md">@​embedpdf/plugin-render's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-render">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-rotate` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-rotate's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ star...

_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-08-20 14:41:25 +00:00
brios c19926456c build(deps): update built from source dep versions in Dockerfiles (#7412)
# Description of Changes

PR updates built from source dep versions in Dockerfiles

Changes:

* Updated `CALIBRE_VERSION` from 9.4.0 to 9.13.0 in
`docker/base/Dockerfile`.
* Updated `GS_VERSION` (Ghostscript) from 10.06.0 to 10.07.1 in
`docker/base/Dockerfile`.
* Updated `IM_VERSION` (ImageMagick) from 7.1.2-13 to 7.1.2-29 in
`docker/base/Dockerfile`.
* Updated `QPDF_VERSION` is already at 12.3.2, no change.
* Updated `UNOSERVER_VERSION` from 3.6 to 3.7 in both
`docker/base/Dockerfile` and `docker/unoserver/Dockerfile` to align with
the client version and avoid wire mismatches.
* Updated `TASK_VERSION` from 3.49.1 to 3.52.0 in
`docker/embedded/Dockerfile`, `docker/embedded/Dockerfile.fat`,
`docker/embedded/Dockerfile.ultra-lite`, and `engine/Dockerfile`.
<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [X] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [X] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [X] I have performed a self-review of my own code
- [X] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [X] I have run `task check` to verify linters, typechecks, and tests
pass
- [X] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 14:41:07 +00:00
brios 383710c1d2 style(navigation): improve unsaved changes modal (#7508)
# Description of Changes

Improves the design of the unsaved changes modal.


### New
<img width="802" height="466" alt="image"
src="https://github.com/user-attachments/assets/f73bc51d-fb30-4075-a516-1aa0007d2735"
/>


### Old
<img width="908" height="450" alt="image"
src="https://github.com/user-attachments/assets/ef004923-4121-4f28-81f8-e3ee3cd68f78"
/>

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [X] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [X] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [X] I have performed a self-review of my own code
- [X] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [X] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [X] I have run `task check` to verify linters, typechecks, and tests
pass
- [X] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 14:40:39 +00:00
Ludy d8fc68a1a7 ci(e2e): use Playwright container for stubbed matrix (#7576)
# Description of Changes

- Use the pinned official Playwright `v1.58.2-noble` container image.
- Remove the per-browser `playwright install --with-deps` step from the
matrix jobs.
- Keep the browser matrix unchanged for independent Chromium, Firefox,
and WebKit reporting.
- Configure Playwright to use the container's root home directory while
directing Docker to a separate configuration path.
- Add npm retry and offline-cache settings to reduce transient
dependency installation failures.

This prevents three matrix jobs from concurrently downloading Playwright
browsers and Linux dependencies, reducing network-related CI failures
while preserving separate per-browser test results.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 14:21:56 +00:00
Anthony Stirling 96a00cebd1 Pdf ua converter testing (#7301)
# Description of Changes

Adds a PDF/UA converter, an accessibility report, and PDF/A conformance
level A.

**New: `POST /api/v1/convert/pdf/ua`** (Convert tool, "PDF/UA" target).
Tags an untagged PDF, marks
decorative content as artifacts, embeds missing fonts and applies the
document-level PDF/UA
requirements (title, language, tab order, form-field descriptions), then
validates with veraPDF. The
`pdfuaid` declaration is written only if validation passes, so a
returned file never claims more
than it delivers; response headers report whether it was declared, how
many checks still fail and
how many images still need a description.

**New: `POST /api/v1/security/accessibility-report`.** Reports what
fails, what the converter can fix
on its own, what needs a person, and lists the figures needing a
description with the keys the
conversion accepts back. Read-only; does not modify the file. Capped at
100 MB / 2000 pages and
weighted `LARGE_WEIGHT`, since it runs a full veraPDF pass plus the
converter's layout analysis over
every page.

**PDF/A level A.** `pdfa-1a`, `pdfa-2a` and `pdfa-3a` output formats on
the existing
`/api/v1/convert/pdf/pdfa` endpoint. Level A is level B plus tagging, so
the document is tagged
after Ghostscript (which discards any structure tree it is given) and
the level A claim is written
only if veraPDF agrees. Optional `pdfUa=true` additionally declares
PDF/UA alongside PDF/A, again
only if it validates.

Honesty rules the implementation holds to:

- **Never claim a level that was not reached.** If tagging fails, the
file is returned at level B and
is named `_PDFA-2b.pdf`, not `_PDFA-2a.pdf`. With `strict=true` the
request fails outright rather
than returning a level B file against a level A request, and a level B
pass no longer satisfies a
  strict level A request.
- **Never relabel a document's language.** The requested language
(default `en-GB`) is applied only
when the document declares none; a French PDF stays French unless the
caller sets
`overrideLanguage`, and ignoring a requested language is reported as a
warning.
- **Never invent alternative text.** Descriptions come from the caller.
The Convert panel can list
the images needing one (via the report endpoint) and send them back per
figure; any image left
undescribed blocks the conformance claim rather than being papered over.
- **Never certify hidden content.** Marking images decorative, or
suppressing text that could not be
tagged reliably, withdraws the claim instead of passing the checker by
hiding content.

PDF/UA-1 and PDF/UA-2 are both offered; UA-2 raises the file to PDF 2.0
and namespaces the structure
tree, and its test asserts conformance rather than merely reporting it.

Convert steps saved in Automations/Pipelines round-trip their PDF/UA
settings (profile, language,
override, title, font embedding, descriptions).

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 12:00:03 +00:00
Anthony Stirling 50d34fcca5 Add download, rename and duplicate to the file actions menu (#7536)
# Description of Changes
Adds expanded dropdown menu for download, rename and duplicate 

<img width="560" height="380" alt="image"
src="https://github.com/user-attachments/assets/84464f0a-46e1-42cf-8098-26f77888710f"
/>

<img width="560" height="480" alt="image"
src="https://github.com/user-attachments/assets/ed871f98-7f89-4560-869e-9ff514000b6f"
/>

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 11:47:05 +00:00
stirlingbot[bot] 4791d558c5 Update Backend 3rd Party Licenses (#7579)
Auto-generated by stirlingbot[bot]

This PR updates the backend license report based on dependency changes.

Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2026-08-20 11:16:18 +00:00
Ludy 91fc26f10c chore: Bump version to 2.14.3 (#7554)
# Description of Changes

This PR bumps the Stirling PDF application version from `2.14.2` to
`2.14.3` across the project.

Changes include:

- Updated the Gradle project version in `build.gradle` to `2.14.3`.
- Updated the Tauri desktop application version in
`frontend/editor/src-tauri/tauri.conf.json`.
- Updated the AUR package version for `stirling-pdf-desktop`.
- Updated the AUR package version for `stirling-pdf-server-bin`.
- Updated the mocked `appVersion` used by the core frontend server
experience simulations.
- Updated the mocked `appVersion` used by the proprietary frontend
server experience simulations.
- Kept all application, desktop, packaging, and test/simulation version
references synchronized for the `2.14.3` release.

The change prepares the project metadata and packaging configuration for
the `2.14.3` release and prevents different components from reporting or
packaging the previous `2.14.2` version.


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 09:53:28 +00:00
EthanHealy01 f7ed1822c7 Float the editor search when no file is open (#7575)
## What

The super-search work pinned the editor's `WorkbenchBar` visible on
every view except My Files, even with no file open. That left an empty
workbench showing a fully painted bar whose only live control was the
search — download / close / print / save were all disabled, because
those actions only make sense with a file open.

This stops forcing the bar. When nothing is open, only the global search
floats (unpainted, centered), mirroring how the Processor already works.
When a file **is** open, the `WorkbenchBar` renders exactly as before.

Also fixes a smaller Processor issue: its floating search strip was
shorter than the sidebar logo row, so the search sat higher than the
brand. Its height now matches the logo row (51px) so they line up.

## Changes

- **`Workbench.tsx`** — render the `WorkbenchBar` only when a file is
open (or a custom view supplies content); otherwise render the new
floating search. My Files and `hideTopControls` custom views are
unchanged.
- **`WorkbenchFloatingSearch.tsx` / `.css`** (new) — the editor's
`SuperSearch` floated in an unpainted strip, mirroring
`PortalSearchBar`. Its vertical band matches the bar's so opening a file
swaps in the bar without a shift.
- **`PortalSearchBar.css`** — strip height matched to
`.portal-sidebar__logo` (51px) so the Processor search aligns with the
logo.

The notification bell is intentionally out of scope — it ships in a
separate PR.

## Before / after

(ignore the bell icon in the after that’s not live yet)

<img width="2056" height="1077" alt="Screenshot 2026-08-20 at 2 28
17 AM"
src="https://github.com/user-attachments/assets/144f5216-4784-42b2-8c09-afda43577ad0"
/>
<img width="2056" height="1071" alt="Screenshot 2026-08-20 at 2 28
31 AM"
src="https://github.com/user-attachments/assets/63af9303-af8a-48dd-b113-485169fb4924"
/>

- **Editor, no file:** painted bar with disabled buttons → just a
floating search.
- **Editor, file open:** unchanged.
- **Processor:** search now vertically aligned with the logo.

## Testing

- `task frontend:check` — lint (incl. colour linters) + typecheck +
tests (247 files / 2137 tests) all pass.
- Processor alignment verified in Storybook (`Portal/Shell/AppShell`):
logo row, search strip, and search pill share the same vertical center.
- Editor float not verified in-browser (local backend is behind a login
gate); covered by types/tests and reuses the verified Processor pattern.
2026-08-20 09:33:26 +00:00
James Brunton a744102cb6 Support Supporting Files in Pipelines (#7547)
# Description of Changes
Currently in the Processor's Pipelines page, none of the tools which
require supporting files are usable because it's never been hooked up to
the new API to upload supporting files. This PR hooks it up to that so
all tools using supporting files work in the processor. I had to tweak
the type generation a little for this so we have a static map of which
params are for supporting files so we know to handle them differently.
The `Test with a file` button has to work a little differently than the
main run since it's running an ad-hoc pipeline so the files haven't
necessarily been saved to the server yet. In this case, it'll use
whatever local changes the user has made for those pipeline steps, and
for all other steps, it'll just use what's saved in the server.
2026-08-20 08:23:20 +00:00
stirlingbot[bot] 1690cc25cc Update Frontend 3rd Party Licenses (#7573)
Auto-generated by stirlingbot[bot]

This PR updates the frontend license report based on changes to
package.json dependencies.

Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2026-08-20 05:15:37 +00:00
Ludy 088e0ef4e2 deps(frontend): upgrade Cantoo PDF library to 2.8.2 (#7493)
# Description of Changes

This pull request upgrades the frontend PDF dependency from
`@cantoo/pdf-lib` 2.6.5 to 2.8.2.

- Updated `frontend/package.json` to require `@cantoo/pdf-lib` `^2.8.2`.
- Regenerated `frontend/package-lock.json` with `@cantoo/pdf-lib@2.8.2`,
`pako@2.2.0`, and `node-html-better-parser@1.5.9`.
- Added the root npm `pako` override recommended by the upstream
release.
- The upgrade brings upstream parser, object-stream, encryption, form,
PNG, and PDF serialization fixes into the frontend dependency.
- No application API migration was required because the project does not
use the newly added PDF/A, XFA, Factur-X, incremental-update, fontkit,
or page-content-extraction APIs.

The main challenge was validating the broad upstream change set against
the project's actual usage. The frontend typecheck and a direct PDF
create/save/load smoke test passed. The complete `frontend:check` and
`frontend:test` tasks exceeded the available execution timeout without
reporting a test failure.

No related issue.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [x] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing)
for more details.
2026-08-19 18:29:02 +00:00
ConnorYoh 6bae9d516d chore(saas): one task per environment, and make the frontend follow it (#7483)
## The problem

The `dev` profile hardcoded one project ref (`qacaivhsjtftfwtgjvva`) in
five places: the ref, the Supabase URL, the publishable key, the
datasource host and the meter endpoint.

That made it both the shared environment everyone relies on *and* the
only thing you could point the backend at. Testing an open SaaS PR meant
hand-overriding all five via env just to reach that PR's Supabase
preview branch, which is the only place the PR's migrations have
actually been applied. Get it wrong and you see `relation
"stirling_pdf.<new table>" does not exist` for a table the PR added,
which is what happened on
[#7414](https://github.com/Stirling-Tools/Stirling-PDF/pull/7414).

## One task per environment

```bash
task dev:saas         # backend + frontend + engine, against this PR's preview branch
task staging:saas     # backend + frontend + engine, against the shared v3 project

task backend:dev:saas       # backend only, preview branch
task backend:staging:saas   # backend only, v3
```

| | how | vars | project |
|---|---|---|---|
| prod | `PROFILES=none` | `SAAS_DB_*` | the live one |
| staging | `PROFILES=staging` | `SAAS_STAGING_*` | pinned to v3, always
there |
| dev | `PROFILES=dev` | `SAAS_DEV_*` | follows a SaaS PR's preview
branch |

`PROFILES` is still the underlying switch, so the old spelling keeps
working. Production deliberately has no named task: reaching it should
take a conscious `PROFILES=none`, not a tab-complete.

**staging** is the old `dev` configuration, moved and kept pinned. The
value of a shared environment is that it is still there tomorrow:
reproduce a bug, paste a link to a colleague, share data.

**dev** is parameterised by `SAAS_DEV_PROJECT_REF` and derives the
Supabase URL, JWT issuer, JWKS, meter endpoint and (unless overridden)
the database host from it. Switching which PR you are testing is one
variable instead of five. With no ref set, `task backend:dev:saas` stops
and says what to set rather than falling back.

## The frontend was the real gap

`frontend/editor/.env` is committed and pins the **production** Supabase
project, and nothing in the frontend knew about dev or staging. So `task
dev:saas` gave you a backend on a preview branch and a login against
prod, unless you happened to have hand-written
`frontend/editor/.env.saas.local`.

The dev tasks now read the backend's env files and derive
`VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY` from the
same project ref the backend resolved, so the two halves cannot point at
different projects. Nothing to keep in sync by hand, no new vite mode,
and `SAAS_ENV=prod` opts back out to the committed values.

## Where to put your local values

Two files, both gitignored, neither ever committed:

**`app/.env.saas.local`** is the only one you normally need. The tasks
load it for the backend *and* the frontend.

```bash
# staging: everything else is already defaulted, so this is all it takes
SAAS_STAGING_DB_PASSWORD=...

# dev: the preview branch of the PR you are testing, from its "Supabase Preview" check.
# A branch has its OWN password and API keys; the parent project's will not authenticate.
SAAS_DEV_PROJECT_REF=...
SAAS_DEV_DB_PASSWORD=...
SAAS_DEV_PUBLISHABLE_KEY=...

# prod, if you ever need it
SAAS_DB_PROJECT_REF=...
SAAS_DB_URL=...
SAAS_DB_PASSWORD=...
SUPABASE_EDGE_FUNCTION_SECRET=...
```

**`frontend/editor/.env.saas.local`** is no longer needed for choosing a
Supabase project, and is best left empty or deleted. If you have one
from before this PR, note that the task-supplied values now win, which
is the point: the frontend follows the backend.

**A blank is not the same as absent.** A dotenv line with an empty value
still *sets* the variable, and Spring's `${VAR:default}` only falls back
when a variable is absent. So `.env.saas` lists what you must set as
blanks, and leaves out the two `*_DB_URL` overrides, which have real
defaults to fall back to. This is not theoretical, see below.

Committed `app/.env.saas` holds non-secret defaults only. Real secrets
are passwords, the edge-function secret and service-role keys. Project
refs and publishable keys are neither: a ref is the public
`<ref>.supabase.co` subdomain and a publishable key ships in the browser
bundle by design, which is why `frontend/editor/.env` has always carried
prod's.

## Three bugs found while building the tasks

All three were in this PR's own earlier commits, and all three were
caught by actually booting things rather than by reading the config.

**staging could not boot at all.** A blank `SAAS_STAGING_DB_URL=` in
`.env.saas` set the variable to empty, so
`${SAAS_STAGING_DB_URL:jdbc:...}` resolved to `""` and startup failed
with `spring.datasource.url is required when the saas profile is
active`. The file already carried a comment warning about exactly this;
it had only been applied to the dev block. The original verification for
this PR was "placeholders resolve" and "the task parses", neither of
which boots anything.

**The dev to staging fallback ran `ddl-auto=update` against shared v3.**
The dev profile sets `update`, which is right for a disposable preview
branch, and separately fell back to staging's project ref. Together that
meant Hibernate was free to reconcile tables that RLS policies depend
on. `application-staging.properties` pins `none`, but that only applies
when the staging profile is the active one, which it was not on the
fallback path. There is no fallback now: with no ref the task stops
before gradle, and the frontend fails the same way, both naming the
variable.

**`PROFILES=` never selected production.** Go template `default` treats
`""` as absent, so it silently resolved back to `dev`. It is
`PROFILES=none` now.

## Two choices worth reviewing

**Staging keeps its committed project ref**, now as a
`${SAAS_STAGING_PROJECT_REF:...}` default in one place, with the URL,
database host and meter endpoint all derived from it. So staging still
works with zero setup, and repointing it is one variable. Nothing in CI
referenced the ref or the profile. Its publishable key default carries
no inline `gitleaks:allow`: a trailing comment in a `.properties` file
is part of the value, so the pragma ended up inside the key. It is in
`.gitleaksignore` instead.

**`SAAS_DEV_DB_URL` still overrides the whole URL**, so a branch needing
the pooler host rather than the direct one is reachable without touching
committed config.

## Verification

- `task backend:staging:saas` boots against v3 and serves `200`. It
could not boot before this commit.
- `task backend:dev:saas` with no ref stops before gradle naming the
variable, and `PROFILES=none` still reaches production. `task
frontend:dev:saas` fails the same way; `SAAS_ENV=staging` still resolves
with no local config.
- Frontend routing picks the SaaS runner for dev/staging and the plain
runner for prod; the derivation returns the right URL and key for each.
- Vite's `process.env` precedence and Task's dotenv/env semantics were
measured, not assumed. That is how one trap surfaced: Task sets an
`env:` key even when its value resolves to empty, and Vite treats an
empty `process.env` `VITE_*` as authoritative over a committed `.env`.
Putting the Supabase vars on the shared `dev:_run` would have blanked
Supabase config for the core, proprietary and desktop dev servers, so
the SaaS path has its own runner.
- `:saas:spotlessApply` and `:saas:compileJava` green.

`DevProfileProjectNotice` becomes `SaasProjectNotice` and covers both
profiles, stating the project ref and `ddl-auto` at startup so which
environment you are on is never a guess.

No behaviour change for prod: the `saas` profile is untouched.
2026-08-19 14:46:00 +00:00
EthanHealy01 0f8803f35f Require the policy-management role to run a policy against its sources (#7565)
## What

Running a stored policy against its **configured sources** (`POST
/api/v1/policies/{id}/trigger`, the manual "run now") now requires the
policy-management role — global admin self-hosted, team leader on SaaS —
alongside the existing team scoping.

## Why

A source sweep operates on the team's configured sources using the
server's stored connection credentials, so it belongs with the other
policy-management capabilities rather than with ordinary use. Team
scoping on its own didn't express that distinction.

## Not changed

- `POST /{id}/run` — running a policy over documents the **caller
supplied** stays open to every team member. That's ordinary editor
enforcement on upload and export, and gating it would break it.
- Ad-hoc pipelines (`/run`, `/run/stream`).
- The scheduled, folder-watch and webhook triggers.
- Single-user deployments (login disabled), which have no roles.

## Implementation

`PolicyManagementAuthority` gains `canTriggerPolicies()`, kept separate
from `canEditPolicies()` so the two capabilities can diverge later. Both
current implementations grant it to the same principals that may edit
policies.

## Tests

- role absent → 403, rejected before any run starts
- role present → 202
- login disabled → check skipped entirely
- `/{id}/run` asserted to consult neither authority method, so the gate
can't quietly extend to the editor path later
2026-08-19 14:27:39 +00:00
Anthony Stirling 6f7f28946c Set deployment: false on environment jobs that do not deploy (#7562)
# Description of Changes

thanks ludy for the tip :P 

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-19 14:19:26 +00:00
Ludy ec3de16c08 ci: centralize Gradle caching across GitHub Actions workflows (#7546)
## Summary

This pull request restructures Gradle dependency caching across the
GitHub Actions workflows.

The central `gradle-cache-prime` job is responsible for preparing the
shared backend Gradle cache. Reusable workflows restore that shared
cache without writing to the same key, while independently triggered
workflows use isolated cache namespaces.

## What changed

### Shared Gradle cache

- Added a stable `gradle-v1-` cache namespace for the shared backend
cache.
- The cache key includes the runner OS, runner architecture, JDK
version, and the relevant Gradle configuration files.
- The cache key is calculated before Gradle runs and reused for the
later save step.
- The prime job performs a lookup first and resolves backend
dependencies only when the exact cache is missing.
- This prevents Gradle or Spotless changes during the prime step from
producing a different save key from the key used by downstream jobs.

### Reusable workflows

- Backend, OpenAPI, license, Docker, E2E, and migration workflows
restore the shared cache instead of writing to the shared key.
- The backend build matrix includes `matrix.jdk-version` in its cache
key.
- Enterprise, Tauri, and generated-model workflows support the
`use_shared_cache` boolean input.
- When `use_shared_cache` is enabled, those workflows restore the shared
cache.
- When it is disabled, they use workflow-specific cache namespaces.

### Independent workflows

Independent workflows now use separate cache prefixes, including:

- `gradle-license-report-v1-`
- `gradle-swagger-v1-`
- `gradle-push-docker-v1-`
- `gradle-tauri-releases-v1-`
- `gradle-deploy-pr-v1-`
- `gradle-playwright-e2e-v1-`
- `gradle-generated-models-v1-`

This prevents them from creating or affecting the shared backend cache
before the prime job.

### Build and E2E flow

- Removed the `-PnoSpotless` option from the central Gradle
dependency-resolution command.
- Removed the separate Gradle dependency prime/retry logic from the live
E2E workflow.
- Connected the Tauri build and generated-models check to the central
cache-prime job.

## Motivation

Previously, multiple workflows could use and save the same Gradle cache
key independently. The first workflow to save the cache could therefore
determine its contents, even if it had resolved a different or
incomplete set of dependencies.

The cache key was also evaluated after some Gradle tasks had run. If
Gradle or Spotless modified a file covered by `hashFiles(...)`, the save
key could differ from the restore key used by downstream jobs.

This change gives the shared cache a single owner, isolates
workflow-specific caches, and makes cache usage deterministic across the
CI pipeline.

## Expected result

- `gradle-cache-prime` is the single writer for the shared backend
Gradle cache.
- Downstream jobs restore the same cache without competing cache writes.
- Independently triggered workflows remain isolated through their own
cache namespaces.
- Changes to the monitored Gradle configuration files produce a new
cache key.
- The normal Gradle/Spotless path is included when the shared cache is
populated.

## Validation

- Compared the cache key expressions and `hashFiles(...)` inputs across
the affected workflows.
- Verified that the central restore and save steps use the same
precomputed key.
- CI should confirm that the prime job populates the shared cache and
downstream workflows only restore it.

## Checklist

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have performed a self-review of my changes
- [ ] I have run the relevant CI checks
- [ ] I have tested the workflow changes
2026-08-18 19:25:55 +01:00
EthanHealy01 913601ff03 Consolidate the editor + processor sidebar footers into one component (#7539)
## What

Both sidebars ended in a different bottom section. The editor showed an
account row (avatar, name, settings); the processor showed a "Link
Stirling account" CTA plus a `Settings` nav item and no identity at all.
They are now **one shared `<NavFooter>`** rendering the same rows in
both apps, in this order:

1. the link-account CTA (self-hosted, when unlinked)
2. free credits remaining
3. **Open \<the other app\>**
4. the account row — avatar, name, settings

It's a **single surface** with hairline dividers between rows, not
stacked cards. Rows are assembled as a list, so a row this build doesn't
show (no wallet, no processor access, nothing to link) takes its divider
with it rather than leaving a stray line.

This also fixes the profile-picture/initials desync between the sidebar
and the account settings page.

## Screenshots

Captured with the stubbed Playwright harness at 1600x900, scoped to the
sidebar and auto-cropped to the region that actually changed. Base is
`origin/main`; every state is driven by dummy backend stubs so all the
nav-bar permutations are covered.

<img width="2104" height="3044" alt="montage_cloud-dark"
src="https://github.com/user-attachments/assets/667c9a71-3ab7-4582-9259-08cda521e238"
/>
<img width="2104" height="3044" alt="montage_cloud-light"
src="https://github.com/user-attachments/assets/126bc994-efa3-436b-bf50-31d76f574eaa"
/>
<img width="2104" height="1492" alt="montage_editor-dark"
src="https://github.com/user-attachments/assets/c725726d-ea90-4fa9-bf0a-6014f3729869"
/>
<img width="2104" height="1490" alt="montage_editor-light"
src="https://github.com/user-attachments/assets/d0d34e0a-ac60-4f8c-af15-afb66fbd679e"
/>
<img width="2104" height="1066" alt="montage_processor-dark"
src="https://github.com/user-attachments/assets/51ccfe63-c327-41f4-ab91-74555b6f7148"
/>
<img width="2104" height="1068" alt="montage_processor-light"
src="https://github.com/user-attachments/assets/c90d7aba-90fa-4998-ac8f-26ce666dde71"
/>


The free-credits meter is a cloud-build surface, so the self-hosted
capture can't reach it. Those states come from the new Storybook stories
with dummy wallet data (`Shared/NavFooter`), which is also where the
credit tone bands and the collapsed rail are easiest to review.

## How it's wired

`NavFooter` is purely presentational. Each app resolves its own data
through three `@app/*` seams, so core carries no build-specific gating
and any box whose data is absent is dropped rather than rendered empty.

| Seam | core | cloud / proprietary / saas |
|---|---|---|
| `useFreeCreditsSummary` | `null` — self-hosted editor installs aren't
metered | cloud reads `freeRemaining` / `freeAllowance` off the same
`useWallet()` the Plan page's free meter uses, so the sidebar and Plan
can't disagree |
| `useOtherAppSwitch` | `null` — core ships no processor | gated on
`portalAccess` (`/api/v1/auth/me` in SaaS, the Spring session flag
self-hosted) |
| Link-account CTA | n/a | unchanged conditions — passed in as
`accountExtras`, still only when `linkState === "unlinked"`, still a
no-op in SaaS |

- The processor reads the meter through its own
`@portal/hooks/useFreeCreditsSummary` rather than the editor's `@app`
one. Self-hosted resolves `@app/*` as proprietary → core, where the
cloud wallet hook isn't in the cascade, and the implementation can't
live in `proprietary/` because core/desktop builds ship no portal and
must never resolve `@portal`. Keeping it in `portal/` gets the figure to
the linked self-hosted processor without weakening that rule; it reads
the same `GET /api/v1/payg/wallet` the Usage page's trial meter already
renders, gated on link state and behind the portal's query cache.
`portal-saas/` just re-exports the cloud hook, so both footers share one
fetch.

The processor-access gate previously lived in two near-identical
`AppSwitcher` copies. It moves into `useOtherAppSwitch`, `AppSwitcher`
now reads it too, and the duplicate
`saas/components/shared/AppSwitcher.tsx` is deleted — the logo switcher
and the footer row can no longer disagree about access.

## Profile picture sync

One `useAccountIdentity` hook now backs the editor footer, the processor
footer and the account settings page. Previously settings derived its
initial from `email[0]` while the sidebar used `displayName[0]`, and the
two drew different blue discs. Alongside that, the shared `Avatar`:

- falls back to initials when a picture URL fails to load, instead of
leaving an empty disc
- renders one letter for single-word names (`admin` → "A", not "AD")
- gains an `xl` size so the settings hero disc is the same component

## Notes

- Labelled **"Free credits"** rather than "free monthly credits":
`freeAllowance` is documented as a one-time lifetime grant, not a
monthly reset, so "monthly" would misdescribe the data. Happy to change
if the backend semantics differ from the type comments.

## Testing

- `task frontend:check` and `task frontend:typecheck:all` pass (all 9
build variants).
- 9 new `Shared/NavFooter` stories pass the Chromium + axe story scan;
`frontend:storybook:a11y:changed` reports no regressions.
- Stubbed E2E suite passes, including the `config-button` tour/settings
specs that target the account row. Two failures (`console-clean ›
landing`, `viewer-text-selection › Ctrl+C`) also fail on `origin/main`
locally — they need a backend on :8080 and clipboard permissions.
2026-08-18 14:00:43 +00:00
EthanHealy01 cf49742d97 Fix the top bar styling (#7544)
Every top bar styled itself, so none of them matched the new UI. Also,
colors on the premium banner (and possibly others) clashed since the
theme changes.

## Before Example Issue

<img width="1934" height="348" alt="Screenshot 2026-08-17 at 11 47
20 PM"
src="https://github.com/user-attachments/assets/b6f13207-2f47-4084-bd3b-2392f572c1a1"
/>


## After (all)

<img width="2880" height="800" alt="danger__dark"
src="https://github.com/user-attachments/assets/6b311dec-23e7-4059-a6bb-75527cbd2e34"
/>
<img width="2880" height="800" alt="danger__light"
src="https://github.com/user-attachments/assets/3b04b109-5146-4874-88b3-d99770ea51f8"
/>
<img width="2880" height="800" alt="default-app__dark"
src="https://github.com/user-attachments/assets/b0101b98-fce8-467a-99a6-dd40b8864da1"
/>
<img width="2880" height="800" alt="default-app__light"
src="https://github.com/user-attachments/assets/8aa21f01-6549-4d78-b217-c5547d60ab5b"
/>
<img width="2880" height="800" alt="free-tier-limit__dark"
src="https://github.com/user-attachments/assets/07fd7498-f44f-408f-8c79-9b5ea55e13df"
/>
<img width="2880" height="800" alt="free-tier-limit__light"
src="https://github.com/user-attachments/assets/f38c527b-9f64-4db0-b84c-56ca48e464cc"
/>
<img width="2880" height="800" alt="server-attention__dark"
src="https://github.com/user-attachments/assets/447a36fa-056d-4ca9-8b30-04aa0ccd6ed1"
/>
<img width="2880" height="800" alt="server-attention__light"
src="https://github.com/user-attachments/assets/f0311d45-a218-4846-b0ac-47996e2637c5"
/>
<img width="2880" height="800" alt="team-invitation__dark"
src="https://github.com/user-attachments/assets/cc368473-3b9f-4ab0-878a-67da993874c2"
/>
<img width="2880" height="800" alt="team-invitation__light"
src="https://github.com/user-attachments/assets/19d52a3e-4028-46f0-8562-9bd9cef9397a"
/>
<img width="2880" height="800" alt="upgrade-prompt__dark"
src="https://github.com/user-attachments/assets/e9d20daa-f41f-46d9-b827-a84f276e8af1"
/>
<img width="2880" height="800" alt="upgrade-prompt__light"
src="https://github.com/user-attachments/assets/9b6250c1-6a61-40d6-a7ab-e37398d67322"
/>


## What changed

- `InfoBanner` exposed 8 colour-override props (`background`,
`borderColor`, `textColor`, `iconColor`, `buttonColor`,
`buttonTextColor`, `closeIconColor`, `buttonVariant`), so every caller
invented its own look. Replaced with a closed tone set: `info` · `promo`
· `warning` · `danger`.
- Tone drives the whole bar — fill, border, icon and the button — so a
CTA can't drift from the bar it sits on. Text is neutral in every tone;
only the icon carries the tone colour.
- All colour comes from `--c-*` tokens mixed over `--c-surface`, so the
bars follow light and dark instead of ignoring them. The old bars were
hardcoded: in dark mode the two licence warnings stayed cream-on-white.
- `promo` keeps the gradient it was always meant to have, built from the
existing `--c-hue-indigo`/`--c-hue-purple` stops (documented in
`colors.css` as gradient hues, deliberately not accent-following), with
the existing `premium` button accent on it.
- Deleted the hardcoded colours from all four callers: the purple
gradient (`#667eea`→`#764ba2`), the orange soup (`#FFF4E6` / `#9A3412` /
`#EA580C`) duplicated across the urgent banner and the admin plan
section, and the fixed dark bar (`--mantine-color-dark-7`) on the team
invitation.
- `UpgradeBanner|AdminPlanSection` sat on the theme linter's exemption
list, which is how those colours survived the theme migration. Exemption
removed, so `code-colors` now guards them.
- The banner's class was colliding with `core/ui/Banner.css`'s
`.sui-banner` (16 live rules), which restyled it in the app but not in
Storybook — that's why the two disagreed on radius, border and tone.
Renamed to `.app-banner`; the two surfaces now render identically.
- Bar is square and full-bleed with a single hairline rule underneath;
button labels are optically centred.
- Added `--c-warning-subtle`, matching the existing `--c-danger-subtle`
/ `--c-success-subtle`.
- New `Shared → Top bars` story renders all six bars at once, so a
change to the shared component is visible against the whole set.
- Unrelated one-liner: `frontend/.prettierignore` now ignores the
gitignored `editor/screenshots/` capture artifacts, which were failing
`format:check` locally. Happy to drop it if you'd rather keep this PR to
the bars.

## Testing

- `task frontend:check` — typecheck, lint (oxlint + 4 theme-lint passes
+ stylelint), format, 244 files / 2119 tests.
- `frontend:storybook:a11y:changed` — clean in light and dark.
- The a11y gate caught a real defect mid-change: giving each banner
`role="region"` with the same label produced duplicate landmarks, which
the app hits for real whenever two banners show at once. Landmark
removed.
- All six bars captured in the running editor, light and dark, and
diffed against `origin/main`'s component rendered with each caller's
original props.
2026-08-18 13:56:47 +00:00
James Brunton fb70fc13da Fix tools which crash in the Pipelines page (#7538)
# Description of Changes
Overlay PDFs and Change Metadata both crashed in the Processor because
they required `FilesModalContext` and `ViewerContext` respectively.
Neither of those contexts make sense to provide in the Processor because
there are no files in context and there is no Viewer, so redesign both
tool settings to only optionally require these contexts. Their behaviour
is unchanged in the Editor but they now work in the Processor (just
without the extra info about the active files, since there are none).

Also hooks up the Reorganise Pages settings so that it can be used from
Automate. The component already existed but just wasn't being used,
which just looks like an oversight.
2026-08-18 13:08:23 +00:00
James Brunton a14eec94ec Fix corner radius on Mantine checkboxes in Processor (#7537)
# Description of Changes

## Before
<img width="314" height="311" alt="image"
src="https://github.com/user-attachments/assets/93239e47-8dd4-4174-8a51-7a4a2494048f"
/>

## After
<img width="399" height="324" alt="image"
src="https://github.com/user-attachments/assets/e2318559-006f-41e4-ac69-8c577ae60b89"
/>
2026-08-18 09:02:17 +00:00
Anthony Stirling 89d8ffec5d Ci/environments cleanups, new envs and master to release naming (#7511)
# Description of Changes

Ci/environments cleanups, new envs and master to release naming

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-17 17:49:13 +00:00
EthanHealy01 526bb85e17 Translate the failures debug panel strings (#7500)
Follow-up to #7296, addressing a missing translation.
2026-08-17 17:00:35 +00:00
James Brunton 99dda7a9f6 Convert Processor tables to be consistent with each other (#7312)
# Description of Changes
Currently, the tables in the processor are inconsistently styled and
function differently to each other because they don't all share the same
table component. This is a big problem since the Processor is a largely
table-based UI and most of most pages in it are tables. This PR creates
a new common `DataTable` component and converts all existing tables in
the Processor (other than those in the docs pages) to use it, tweaking
the individual tables content as necessary so they all work the same.

The `DataTable` component is designed to take minimal JSX as arguments.
Instead, it takes typed data from the caller and it converts that into
JSX, which makes it a lot easier to guarantee that the tables will all
render the same and if we want to update the table styling in the
future, we just need to do it in one place. I also added sorting
capabilities to many of the tables since it was easy with the library
and it was appropriate for many of the existing table columns.

## Table Comparisons

<img width="2296" height="861" alt="AuditTab-comparison"
src="https://github.com/user-attachments/assets/92473508-3f93-45c4-9485-ee21d0d1ecd0"
/>

<img width="2296" height="617" alt="DocumentExtractions-comparison"
src="https://github.com/user-attachments/assets/cf194210-f4c9-42d1-bce1-5a70c0f8fdc5"
/>

<img width="2296" height="369" alt="InstanceHealthTable-comparison"
src="https://github.com/user-attachments/assets/4b048c62-4237-4043-ae89-047df0cd7e60"
/>

<img width="2296" height="2304" alt="Integrations-comparison"
src="https://github.com/user-attachments/assets/9d9edead-8da0-4d49-a249-253579cfdb0e"
/>

<img width="2296" height="521" alt="InvoicesList-comparison"
src="https://github.com/user-attachments/assets/2b6d40b7-d63d-404b-a834-79d5fc62ba9d"
/>

<img width="2296" height="348" alt="LinkedInstancesTable-comparison"
src="https://github.com/user-attachments/assets/fb65c660-baa4-4fcd-8893-05be1962fdab"
/>

<img width="2296" height="356" alt="PendingInvitations-comparison"
src="https://github.com/user-attachments/assets/1f0105ed-b1f8-4e4e-998f-57024875a28f"
/>

<img width="2296" height="306" alt="PipelinesTable-comparison"
src="https://github.com/user-attachments/assets/d84255bd-37f3-4245-b2b5-ef8c06c000ae"
/>

<img width="2296" height="652" alt="PolicyCatalogue-comparison"
src="https://github.com/user-attachments/assets/55f25f20-46f0-47c0-aa40-516cbd5acab5"
/>

<img width="2296" height="1221" alt="ReviewQueueTable-comparison"
src="https://github.com/user-attachments/assets/2206a50c-731f-48cc-958f-aad5e30e47b0"
/>

<img width="2296" height="344" alt="SourcesTable-comparison"
src="https://github.com/user-attachments/assets/76b54efb-54ea-42a8-a0b1-b0cb21f73296"
/>

<img width="2296" height="659" alt="UsersDirectory-comparison"
src="https://github.com/user-attachments/assets/524d1b63-efcf-4ac8-a07d-11aa1849c189"
/>
2026-08-17 11:47:08 +00:00
ConnorYoh f15832b2bb chore(saas): make schema ownership explicit and enforce it (#7489)
## The problem

The SaaS database has two writers and always has: the Supabase
migrations in the SaaS repo, and Hibernate's `ddl-auto`. That was a
convention rather than a rule, and it leaked twice.

- An older `ddl-auto` run widened `team_memberships.role` to
varchar(255), which needed [a dedicated
migration](https://github.com/Stirling-Tools/Stirling-PDF-SaaS/blob/v3/supabase/migrations/20260804000000_fix_team_memberships_role_varchar50.sql)
to repair, because RLS policies depended on the column.
- `payg_instance_usage` shipped with an entity and **no migration**, and
nobody noticed for months — staging already had the table from an
earlier `ddl-auto` run. It surfaced only when a fresh preview branch,
built from migrations alone, threw `relation does not exist`.

Both are the same bug: nobody had to *say* who owned a table, so the
answer got decided by accident.

## The fix

`SaasSchemaOwnership` is the register — **29 migration-owned, 29
inherited** and left to Hibernate.

`MigrationOwnedSchemaFilter` applies it via Hibernate's
`hbm2ddl.schema_filter_provider`, wired on the **saas profile only**.
Hibernate is never shown a migration-owned table, so it cannot create,
alter, drop or truncate one whatever `ddl-auto` is set to. Inherited
tables stay managed, so a fresh preview branch still heals itself on
first boot. Self-hosted is untouched — there Hibernate rightly owns
everything.

**Why a filter rather than just `ddl-auto=none`:** off, and a fresh
branch is missing the 29 inherited tables. On, and Hibernate can reach
the other 29. The filter is what lets both be true at once.

**Why per-table, not per-schema:** Hibernate's schema management runs
over every mapped entity regardless of namespace. Moving SaaS tables to
their own schema would *not* by itself keep Hibernate out of them —
worth knowing, because that was the intuitive fix and it doesn't work.

## The part that makes it stick

`SaasSchemaOwnershipTest` makes the register binding: every `@Entity` on
the SaaS classpath must appear in exactly one set, so **a new entity
fails the build until someone states who owns its table**. That's the
forcing function that would have caught `payg_instance_usage`.

I verified it bites rather than assuming it — removing a single entry
fails with:

```
These entity tables are not declared in SaasSchemaOwnership, so nobody owns them.
Offending tables -> entities: [policies (stirling.software.proprietary.policy.store.PolicyEntity)]
```

naming both the table and the class, which is what the next person
actually needs.

## One debatable call

The **validate** filter excludes them too. Letting validation through
would flag drift, which is genuinely useful — but `ddl-auto=validate`
fails startup, and it would fail on differences we've deliberately
accepted (`ai_create_sessions` carries columns from a reverted Typst
feature that nothing maps). A boot failure over a table we chose not to
manage is noise. Argued in the javadoc; happy to flip it if you'd rather
have the signal.

## Dependency

Depends on
[Stirling-PDF-SaaS#324](https://github.com/Stirling-Tools/Stirling-PDF-SaaS/pull/324),
which adds migrations for the four SaaS-owned tables that had none.
They're listed here as migration-owned on that basis, so #324 should
land first.

Companion to
[#7483](https://github.com/Stirling-Tools/Stirling-PDF/pull/7483)
(dev/staging profiles with per-profile `ddl-auto`).

## Verification

`:saas:test` green including the 5 new tests, `spotlessCheck` green, and
the mutation check above.
2026-08-17 10:08:09 +00:00
EthanHealy01 08b08aa8a1 Let everyone read the failures they caused (Review Flow PR 3) (#7477)
Review Flow PR 3. Stacked on #7296. A recorded failure becomes readable
by the person who caused it.

## What changes

Before this, reading or triaging a failure required leader permissions:
`FileRunEventController.requireFailureReviewAllowed()` returned 403 to
anyone who could not edit policies. #7296 lets any user report a
failure, so they could file into a queue they could never read.

That gate is removed from the endpoints and the decision moves into
`FileRunEventService`:

| Caller | Reads and closes |
|---|---|
| Team leader or admin | the whole team's failures (unchanged) |
| Anyone else | only failures where `actor` is them |
| Team unresolvable | nothing |
| Name unresolvable | nothing |

`GET /kinds` is also opened. It returns static enum metadata, and a
member needs it to render failures they can already see.

## Additions

- An `actor` predicate on both list queries in `FileRunEventRepository`,
threaded through `FileRunEventStore.list`.
- `ReadScope` (permitted, teamId, actor) replacing `TeamScope`, with
`wholeTeam` / `mine` / `denied` factories.
- An actor filter on `dispatch`, so acting on another person's row
answers **404, not 403** — the same response as an id that does not
exist.

## Fixes

- **`report()` filed rows under the wrong team.** It took the team from
the read scope, which returns null for a caller who cannot be named, so
such a report landed unteamed in the bucket every team shares. It now
uses a dedicated `currentTeamId()`.
- **`forgetFiles` narrows to the caller even for a leader.** File ids
are minted by each client, so scoping on team alone would let one caller
close a colleague's incidents by naming ids.
- The controller no longer injects `PolicyManagementAuthority` or
`ApplicationProperties`; with the gate gone it decides nothing.

## Team isolation

Unchanged and covered by database-backed tests rather than mocks.
`FileRunEventStoreDbTest` asserts that a caller with a team sees only
their own team's rows and never the unteamed ones, and that the actor
predicate narrows within a team without ever widening across one. Delete
either clause from the JPQL and one of those tests fails.

No endpoint accepts a team parameter; the team always comes from the
authenticated principal.

**Attribution is fixed here too, because this PR depends on it.** A
failure's actor was read from the MDC audit principal, which carries the
BILLING identity — for a stored policy, always its owner. Since reads
are now narrowed to the rows you are the actor on, a wrong actor means
the member who caused a failure and holds the document reads nothing,
while the policy owner is handed incidents from runs they never
triggered. The triggering user is now carried on the run, separate from
the billing principal and the output owner, and is null for a
trigger-fired sweep so an unattended failure stays ownerless.

`PolicyFailureAttributionTest` runs the real engine, recorder, store and
service together. The two sides used to assert independently — the
engine's test matched the actor with `any()`, which is how this went
unnoticed.

## How to test

Needs a proprietary or SaaS build with login enabled and two accounts in
the same team, one a leader and one not. `task dev:all` gives you the
stack.

1. **As the member**, fail a tool: open a PDF and run **Remove
Password** with a wrong password.
2. **Still as the member**, go to `/processor/documents` → **Failures**.
Before this PR you got nothing here. Now you see your own row, and only
yours.
3. **As the leader**, open the same view. You see the whole team's rows,
including the member's.
4. **Member cannot reach a colleague's row.** As the leader, copy a
row's id from **Show raw JSON**. As the member, `POST
/api/v1/file-run-events/{thatId}/actions/DISMISS`. It answers **404**,
and the row is untouched — it must not answer 403, which would confirm
the row exists.
5. **Member can close their own.** Dismiss your own row as the member.
It leaves the default view.
6. **Deleting a file only closes your own rows.** As the leader, delete
a file in your editor. The member's incidents are untouched even if the
leader's client happened to name the same ids.

## Migration

None. `actor` is an existing column; this only adds predicates to
existing queries.
2026-08-16 22:27:20 +00:00
Ludy 2c89f5fe52 fix(ci): locale path and docs (#7525)
# Description of Changes

Update the TOML locale check to match the current frontend path and
align comments/docs with the relocated editor and proprietary source
layout. This keeps CI filtering and internal references accurate after
the frontend reorganization.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-16 17:17:16 +01:00
dependabot[bot] 9a90797041 build(deps): bump awsSdkVersion from 2.51.2 to 2.51.3 (#7529)
Bumps `awsSdkVersion` from 2.51.2 to 2.51.3.
Updates `software.amazon.awssdk:s3` from 2.51.2 to 2.51.3

Updates `software.amazon.awssdk:url-connection-client` from 2.51.2 to
2.51.3


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-16 17:15:47 +01:00
dependabot[bot] 9c10204cb0 build(deps): bump Swatinem/rust-cache from 2.9.1 to 2.9.2 (#7531)
Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from
2.9.1 to 2.9.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/swatinem/rust-cache/releases">Swatinem/rust-cache's
releases</a>.</em></p>
<blockquote>
<h2>v2.9.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Typofix by <a
href="https://github.com/23Skidoo"><code>@​23Skidoo</code></a> in <a
href="https://redirect.github.com/Swatinem/rust-cache/pull/316">Swatinem/rust-cache#316</a></li>
<li>fix: include target names in build/ and .fingerprint/ cleanup by <a
href="https://github.com/eitsupi"><code>@​eitsupi</code></a> in <a
href="https://redirect.github.com/Swatinem/rust-cache/pull/317">Swatinem/rust-cache#317</a></li>
<li>fix: include cdylib/rlib/dylib/staticlib targets in build and
fingerprint cleanup by <a
href="https://github.com/eitsupi"><code>@​eitsupi</code></a> in <a
href="https://redirect.github.com/Swatinem/rust-cache/pull/320">Swatinem/rust-cache#320</a></li>
<li>Scan content of <code>$CARGO_HOME/bin</code> on restore instead of
relying on <code>cargo install</code> metadata by <a
href="https://github.com/clechasseur"><code>@​clechasseur</code></a> in
<a
href="https://redirect.github.com/Swatinem/rust-cache/pull/325">Swatinem/rust-cache#325</a></li>
<li>docs: Update checkout action version to latest by <a
href="https://github.com/sondrelg"><code>@​sondrelg</code></a> in <a
href="https://redirect.github.com/Swatinem/rust-cache/pull/345">Swatinem/rust-cache#345</a></li>
<li>Fix Windows cache path validation after Rollup migration by <a
href="https://github.com/eitsupi"><code>@​eitsupi</code></a> in <a
href="https://redirect.github.com/Swatinem/rust-cache/pull/355">Swatinem/rust-cache#355</a></li>
<li>fix: support Cargo V2 build dir layout by <a
href="https://github.com/claytonwramsey"><code>@​claytonwramsey</code></a>
in <a
href="https://redirect.github.com/Swatinem/rust-cache/pull/371">Swatinem/rust-cache#371</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/23Skidoo"><code>@​23Skidoo</code></a>
made their first contribution in <a
href="https://redirect.github.com/Swatinem/rust-cache/pull/316">Swatinem/rust-cache#316</a></li>
<li><a href="https://github.com/eitsupi"><code>@​eitsupi</code></a> made
their first contribution in <a
href="https://redirect.github.com/Swatinem/rust-cache/pull/317">Swatinem/rust-cache#317</a></li>
<li><a
href="https://github.com/clechasseur"><code>@​clechasseur</code></a>
made their first contribution in <a
href="https://redirect.github.com/Swatinem/rust-cache/pull/325">Swatinem/rust-cache#325</a></li>
<li><a href="https://github.com/sondrelg"><code>@​sondrelg</code></a>
made their first contribution in <a
href="https://redirect.github.com/Swatinem/rust-cache/pull/345">Swatinem/rust-cache#345</a></li>
<li><a
href="https://github.com/claytonwramsey"><code>@​claytonwramsey</code></a>
made their first contribution in <a
href="https://redirect.github.com/Swatinem/rust-cache/pull/371">Swatinem/rust-cache#371</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/Swatinem/rust-cache/compare/v2.9.1...v2.9.2">https://github.com/Swatinem/rust-cache/compare/v2.9.1...v2.9.2</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md">Swatinem/rust-cache's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>2.9.2</h2>
<ul>
<li>Fix <code>credentials.toml</code> cleanup</li>
<li>Improvements to cleanup, preserving more valid targets</li>
<li>Improvements to <code>cargo install</code> handling</li>
<li>Correctly sort/dedupe Rust versions</li>
</ul>
<h2>2.9.1</h2>
<ul>
<li>Fix regression in hash calculation</li>
</ul>
<h2>2.9.0</h2>
<ul>
<li>Update to <code>node24</code></li>
<li>Support running from within a <code>nix</code> shell</li>
<li>Consider all installed toolchains for cache key</li>
<li>Use case-insensitive comparison to determine exact cache hit</li>
</ul>
<h2>2.8.2</h2>
<ul>
<li>Don't overwrite env for cargo-metadata call</li>
</ul>
<h2>2.8.1</h2>
<ul>
<li>Set empty <code>CARGO_ENCODED_RUSTFLAGS</code> when retrieving
metadata</li>
<li>Various dependency updates</li>
</ul>
<h2>2.8.0</h2>
<ul>
<li>Add support for <code>warpbuild</code> cache provider</li>
<li>Add new <code>cache-workspace-crates</code> feature</li>
</ul>
<h2>2.7.8</h2>
<ul>
<li>Include CPU arch in the cache key</li>
</ul>
<h2>2.7.7</h2>
<ul>
<li>Also cache <code>cargo install</code> metadata</li>
</ul>
<h2>2.7.6</h2>
<ul>
<li>Allow opting out of caching $CARGO_HOME/bin</li>
<li>Add runner OS in cache key</li>
<li>Adds an option to do lookup-only of the cache</li>
</ul>
<h2>2.7.5</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/Swatinem/rust-cache/commit/6323deb102c322ba6fcbdcafc7e3dddab59af2b6"><code>6323deb</code></a>
2.9.2</li>
<li><a
href="https://github.com/Swatinem/rust-cache/commit/b16e8d71b289c3b8fc03fc09764563df03712036"><code>b16e8d7</code></a>
bump rollup and rebuild</li>
<li><a
href="https://github.com/Swatinem/rust-cache/commit/3bf42ac996de475743278f3187c4fd89f23b8630"><code>3bf42ac</code></a>
invert target/profile check in cleanup</li>
<li><a
href="https://github.com/Swatinem/rust-cache/commit/6e5b278ead409e28cd5a784014d7ce55007a81d2"><code>6e5b278</code></a>
correctly sort and dedupe Rust versions</li>
<li><a
href="https://github.com/Swatinem/rust-cache/commit/5adc05f6aaa7c92756cb2e2c9b7b3c1d0df9312b"><code>5adc05f</code></a>
Bump the actions group across 1 directory with 3 updates (<a
href="https://redirect.github.com/swatinem/rust-cache/issues/368">#368</a>)</li>
<li><a
href="https://github.com/Swatinem/rust-cache/commit/66b1e9526150e74ddd7e4190356facd783fb3b44"><code>66b1e95</code></a>
fix: support Cargo V2 build dir layout (<a
href="https://redirect.github.com/swatinem/rust-cache/issues/371">#371</a>)</li>
<li><a
href="https://github.com/Swatinem/rust-cache/commit/72d126e709cad40056a62344eee609d696d62d33"><code>72d126e</code></a>
Merge pull request <a
href="https://redirect.github.com/swatinem/rust-cache/issues/367">#367</a>
from Swatinem/dependabot/npm_and_yarn/dev-patch-2b495...</li>
<li><a
href="https://github.com/Swatinem/rust-cache/commit/48968d2131215f1c516b89399d23900d554f71fa"><code>48968d2</code></a>
Bump the dev-patch group with 2 updates</li>
<li><a
href="https://github.com/Swatinem/rust-cache/commit/9f151aca7c3990bab7afe2d82ac58088d3b01074"><code>9f151ac</code></a>
update dependencies, rebuild</li>
<li><a
href="https://github.com/Swatinem/rust-cache/commit/0e24e5dcecfbbdcea69e0f528cab00df34fbd231"><code>0e24e5d</code></a>
Bump the actions group across 1 directory with 6 updates (<a
href="https://redirect.github.com/swatinem/rust-cache/issues/364">#364</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/swatinem/rust-cache/compare/c19371144df3bb44fab255c43d04cbc2ab54d1c4...6323deb102c322ba6fcbdcafc7e3dddab59af2b6">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Swatinem/rust-cache&package-manager=github_actions&previous-version=2.9.1&new-version=2.9.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 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-16 17:15:37 +01:00
dependabot[bot] beea37cb92 build(deps): bump step-security/harden-runner from 2.20.0 to 2.20.1 (#7466)
Bumps
[step-security/harden-runner](https://github.com/step-security/harden-runner)
from 2.20.0 to 2.20.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/step-security/harden-runner/releases">step-security/harden-runner's
releases</a>.</em></p>
<blockquote>
<h2>v2.20.1</h2>
<h2>What's Changed</h2>
<ul>
<li>AWS CodeBuild-hosted runner support</li>
<li>Implicitly allow single-labeled (internal) domains in
block-mode</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/step-security/harden-runner/compare/v2.20.0...v2.20.1">https://github.com/step-security/harden-runner/compare/v2.20.0...v2.20.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/step-security/harden-runner/commit/b09bb98e06d4d774595224525879c09bc6e98c40"><code>b09bb98</code></a>
Merge pull request <a
href="https://redirect.github.com/step-security/harden-runner/issues/680">#680</a>
from step-security/aws-code-build</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/35cd77bcf669054f67ffd3d2802ee54a4f13b5b6"><code>35cd77b</code></a>
docs: document the Global Block List in the features list</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/bb6dbef4bf53876cd2710acd1d36413620d20fb3"><code>bb6dbef</code></a>
chore: rebuild dist with clean dependency install</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/98f73c5a0d2b2cc518e6fb8d973a0a4dde00ba13"><code>98f73c5</code></a>
chore: update eBPF agent to v1.8.14</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/54193c17a4fa3883977217b9afe20378ebe60b19"><code>54193c1</code></a>
Reapply &quot;feat(runners): detect AWS CodeBuild-hosted runners as
third-party pr...</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/d22dd481cea4e96cedde031cfe600c248b592d54"><code>d22dd48</code></a>
Revert &quot;fix(self-hosted): flush agent events at job end when
deploy-on-self-h...</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/0ff09412fb572363b483a3c86ffe52fe61d9fd19"><code>0ff0941</code></a>
fix(self-hosted): flush agent events at job end when
deploy-on-self-hosted-vm...</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/a3c333d110c8d95f34488a22e0e56742cfb1b14f"><code>a3c333d</code></a>
Revert &quot;feat(runners): detect AWS CodeBuild-hosted runners as
third-party pro...</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/bf94c00d6bba2ae7c4a479b86653039811569968"><code>bf94c00</code></a>
feat(runners): detect AWS CodeBuild-hosted runners as third-party
provider</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/514522c5e449f9e28fc901f770e08a573d413e67"><code>514522c</code></a>
fix(self-hosted): resolve runner user when USER env var is unset</li>
<li>See full diff in <a
href="https://github.com/step-security/harden-runner/compare/bf7454d06d71f1098171f2acdf0cd4708d7b5920...b09bb98e06d4d774595224525879c09bc6e98c40">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-16 14:18:51 +01:00
dependabot[bot] fb0281fc80 build(deps-dev): bump codespell from 2.4.2 to 2.4.3 in /engine (#7456)
Bumps [codespell](https://github.com/codespell-project/codespell) from
2.4.2 to 2.4.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/codespell-project/codespell/releases">codespell's
releases</a>.</em></p>
<blockquote>
<h2>v2.4.3</h2>
<!-- raw HTML omitted -->
<h2>What's Changed</h2>
<ul>
<li>Add 'radback' to dictionary with correction by <a
href="https://github.com/Flo3561"><code>@​Flo3561</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3883">codespell-project/codespell#3883</a></li>
<li>Add 'repetirion' to dictionary corrections by <a
href="https://github.com/Flo3561"><code>@​Flo3561</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3885">codespell-project/codespell#3885</a></li>
<li>Need to specify a version of Python version after all by <a
href="https://github.com/DimitriPapadopoulos"><code>@​DimitriPapadopoulos</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3887">codespell-project/codespell#3887</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3889">codespell-project/codespell#3889</a></li>
<li>Add cases for &quot;modulle&quot; -&gt; &quot;module&quot; by <a
href="https://github.com/utzcoz"><code>@​utzcoz</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3888">codespell-project/codespell#3888</a></li>
<li>Add case &quot;auido&quot; -&gt; &quot;audio&quot; by <a
href="https://github.com/utzcoz"><code>@​utzcoz</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3890">codespell-project/codespell#3890</a></li>
<li>Add credentilas-&gt;credentials and friends by <a
href="https://github.com/peternewman"><code>@​peternewman</code></a> in
<a
href="https://redirect.github.com/codespell-project/codespell/pull/3895">codespell-project/codespell#3895</a></li>
<li>Add the case &quot;cubid&quot; -&gt; &quot;cubic&quot; by <a
href="https://github.com/utzcoz"><code>@​utzcoz</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3891">codespell-project/codespell#3891</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3897">codespell-project/codespell#3897</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3900">codespell-project/codespell#3900</a></li>
<li>Bump codecov/codecov-action from 5 to 6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3902">codespell-project/codespell#3902</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3904">codespell-project/codespell#3904</a></li>
<li>Add <code>magntiude-&gt;magnitude</code> by <a
href="https://github.com/nathanjmcdougall"><code>@​nathanjmcdougall</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3899">codespell-project/codespell#3899</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3909">codespell-project/codespell#3909</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3912">codespell-project/codespell#3912</a></li>
<li>Add the case &quot;instanc&quot; -&gt; &quot;instance&quot; by <a
href="https://github.com/utzcoz"><code>@​utzcoz</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3896">codespell-project/codespell#3896</a></li>
<li>gampad -&gt; gamepad (and plural) by <a
href="https://github.com/julianstirling"><code>@​julianstirling</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3906">codespell-project/codespell#3906</a></li>
<li>Add typos of <code>monotonic</code> and <code>monotonicity</code> by
<a
href="https://github.com/nathanjmcdougall"><code>@​nathanjmcdougall</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3898">codespell-project/codespell#3898</a></li>
<li>Add spelling correction for multipile(s)/vulnerabities. by <a
href="https://github.com/cfi-gb"><code>@​cfi-gb</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3905">codespell-project/codespell#3905</a></li>
<li>Add 'simpilfy -&gt; simplify' by <a
href="https://github.com/alexreinking"><code>@​alexreinking</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3913">codespell-project/codespell#3913</a></li>
<li>fix(packaging): prevent unwanted files and tests from being
installed by <a
href="https://github.com/mikelolasagasti"><code>@​mikelolasagasti</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3911">codespell-project/codespell#3911</a></li>
<li>Add skarhoj-&gt;SKAARHOJ to dictionary corrections by <a
href="https://github.com/peternewman"><code>@​peternewman</code></a> in
<a
href="https://redirect.github.com/codespell-project/codespell/pull/3908">codespell-project/codespell#3908</a></li>
<li>Improve the dictionary by <a
href="https://github.com/algonell"><code>@​algonell</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3914">codespell-project/codespell#3914</a></li>
<li>Add spelling correction for accorss/accors. by <a
href="https://github.com/cfi-gb"><code>@​cfi-gb</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3916">codespell-project/codespell#3916</a></li>
<li>Bump autofix-ci/action from 1.3.3 to 1.3.4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3921">codespell-project/codespell#3921</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3923">codespell-project/codespell#3923</a></li>
<li>Add <code>influecer-&gt;influencer</code> and <code>influnce*</code>
typos to dictionary by <a
href="https://github.com/nathanjmcdougall"><code>@​nathanjmcdougall</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3925">codespell-project/codespell#3925</a></li>
<li>Add typos for <code>excavate</code> and variants by <a
href="https://github.com/nathanjmcdougall"><code>@​nathanjmcdougall</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3926">codespell-project/codespell#3926</a></li>
<li>Dict: Add corrections for memoy by <a
href="https://github.com/mdeweerd"><code>@​mdeweerd</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3924">codespell-project/codespell#3924</a></li>
<li><code>overheda -&gt; overhead</code> by <a
href="https://github.com/George-Ogden"><code>@​George-Ogden</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3919">codespell-project/codespell#3919</a></li>
<li><code>inclusize-&gt;inclusive</code> and variants by <a
href="https://github.com/George-Ogden"><code>@​George-Ogden</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3918">codespell-project/codespell#3918</a></li>
<li>Add spelling corrections for authorization by <a
href="https://github.com/cfi-gb"><code>@​cfi-gb</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3922">codespell-project/codespell#3922</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3927">codespell-project/codespell#3927</a></li>
<li>Don't fix Voight by <a
href="https://github.com/DimitriPapadopoulos"><code>@​DimitriPapadopoulos</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3929">codespell-project/codespell#3929</a></li>
<li>Add spelling corrections for interstect and interstection by <a
href="https://github.com/korli"><code>@​korli</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3928">codespell-project/codespell#3928</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3930">codespell-project/codespell#3930</a></li>
<li>Improve output in interactive mode by <a
href="https://github.com/darkmattercoder"><code>@​darkmattercoder</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3884">codespell-project/codespell#3884</a></li>
<li>feat: support codespell:ignore-next-line directive by <a
href="https://github.com/SAY-5"><code>@​SAY-5</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3931">codespell-project/codespell#3931</a></li>
<li>Add woork-&gt;work and formace-&gt;format and friends by <a
href="https://github.com/peternewman"><code>@​peternewman</code></a> in
<a
href="https://redirect.github.com/codespell-project/codespell/pull/3828">codespell-project/codespell#3828</a></li>
<li>shortctu -&gt; shortcut by <a
href="https://github.com/George-Ogden"><code>@​George-Ogden</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3934">codespell-project/codespell#3934</a></li>
<li>A couple typos by <a
href="https://github.com/DimitriPapadopoulos"><code>@​DimitriPapadopoulos</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3935">codespell-project/codespell#3935</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3937">codespell-project/codespell#3937</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3942">codespell-project/codespell#3942</a></li>
<li>reclaculate-&gt;recalculate by <a
href="https://github.com/adamgann"><code>@​adamgann</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3936">codespell-project/codespell#3936</a></li>
<li>Add spelling correction for improprt. by <a
href="https://github.com/cfi-gb"><code>@​cfi-gb</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3939">codespell-project/codespell#3939</a></li>
<li>Dictionary plasic-plastic by <a
href="https://github.com/julianstirling"><code>@​julianstirling</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3938">codespell-project/codespell#3938</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/codespell-project/codespell/commit/57b21406f092110c18776e39b0bda50d37c945c8"><code>57b2140</code></a>
Read only [tool.codespell] from TOML config (<a
href="https://redirect.github.com/codespell-project/codespell/issues/3975">#3975</a>)</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/23b8d940332bcff6369872b53964d4137c586728"><code>23b8d94</code></a>
[pre-commit.ci] pre-commit autoupdate</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/926c4d6ff083acc62bc04c102e0a9b2091d754f1"><code>926c4d6</code></a>
[pre-commit.ci] pre-commit autoupdate</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/820a3011e0bfe3f90789ba41e751a26656bea7ab"><code>820a301</code></a>
Merge pull request <a
href="https://redirect.github.com/codespell-project/codespell/issues/3967">#3967</a>
from codespell-project/peternewman-patch-1</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/9fe42c964c90f65a9a2841847af4e093c67ec817"><code>9fe42c9</code></a>
Add common misspellings for reseeve-&gt;reserve to dictionary</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/725173c815cee1b781b85ec51eb43dc4f51dcd0f"><code>725173c</code></a>
[pre-commit.ci] pre-commit autoupdate</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/86c13ee4dce0010e8d8119ded33694806e37ca8f"><code>86c13ee</code></a>
[pre-commit.ci] pre-commit autoupdate</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/7b9f36ce634c551107a3a4a5c25782a2245d7658"><code>7b9f36c</code></a>
Bump actions/checkout from 6 to 7</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/bcc1b804a83b8a146586101431b063c07cfcc699"><code>bcc1b80</code></a>
Add spelling corrections for simpe and variants.</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/89584cc1479d0f8dbfd501925673b6be6d460fe1"><code>89584cc</code></a>
[pre-commit.ci] pre-commit autoupdate</li>
<li>Additional commits viewable in <a
href="https://github.com/codespell-project/codespell/compare/v2.4.2...v2.4.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=codespell&package-manager=uv&previous-version=2.4.2&new-version=2.4.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 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-16 14:18:23 +01:00
dependabot[bot] 88db26b001 build(deps): bump the eclipse-temurin group across 3 directories with 1 update (#7453)
Bumps the eclipse-temurin group with 1 update in the /docker/backend
directory: eclipse-temurin.
Bumps the eclipse-temurin group with 1 update in the /docker/base
directory: eclipse-temurin.
Bumps the eclipse-temurin group with 1 update in the /docker/embedded
directory: eclipse-temurin.

Updates `eclipse-temurin` from `2f1da10` to `fbcf915`

Updates `eclipse-temurin` from `2f1da10` to `fbcf915`

Updates `eclipse-temurin` from `2f1da10` to `fbcf915`

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-16 14:17:29 +01:00
dependabot[bot] 0deb6fc3d5 build(deps): bump dorny/paths-filter from 4.0.2 to 4.0.3 (#7465)
Bumps [dorny/paths-filter](https://github.com/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>
<h1>Changelog</h1>
<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>
<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>
<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.4</h2>
<ul>
<li><a href="https://github.com/advisories/GHSA-7hc6-8hq5-9q2m">Escape
multi-line filenames in list-files shell and csv output</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>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</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/7b450fff21473bca461d4b92ce414b9d0420d706...ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dorny/paths-filter&package-manager=github_actions&previous-version=4.0.2&new-version=4.0.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 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-16 14:17:18 +01:00
dependabot[bot] 42c84a1693 build(deps): bump base64 from 0.23.0 to 0.23.1 in /frontend/editor/src-tauri (#7460)
Bumps [base64](https://github.com/marshallpierce/rust-base64) from
0.23.0 to 0.23.1.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/marshallpierce/rust-base64/blob/master/RELEASE-NOTES.md">base64's
changelog</a>.</em></p>
<blockquote>
<h1>0.23.1</h1>
<ul>
<li>Make the tests build again on non-SIMD architectures</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/marshallpierce/rust-base64/commit/069bf7067b949f5c0a92b6ceb82492920502f2c2"><code>069bf70</code></a>
v0.23.1</li>
<li><a
href="https://github.com/marshallpierce/rust-base64/commit/6ab1fb0a5843013557a52c45c84b91f5d1bb87af"><code>6ab1fb0</code></a>
Merge pull request <a
href="https://redirect.github.com/marshallpierce/rust-base64/issues/310">#310</a>
from musicinmybrain/test-on-non-simd-arches</li>
<li><a
href="https://github.com/marshallpierce/rust-base64/commit/7cffce6f971acdf48f83112cbdd63bd61125ba06"><code>7cffce6</code></a>
Fix testing on architectures without unsafe SIMD support</li>
<li><a
href="https://github.com/marshallpierce/rust-base64/commit/e34f9a08c5c89a4641350ac22033f3fa4f5d4d97"><code>e34f9a0</code></a>
Merge pull request <a
href="https://redirect.github.com/marshallpierce/rust-base64/issues/308">#308</a>
from atouchet/com</li>
<li><a
href="https://github.com/marshallpierce/rust-base64/commit/e9240c9a01e0a4934c5619e74740caa6d1f67ce9"><code>e9240c9</code></a>
Remove outdated comment</li>
<li>See full diff in <a
href="https://github.com/marshallpierce/rust-base64/compare/v0.23.0...v0.23.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=base64&package-manager=cargo&previous-version=0.23.0&new-version=0.23.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 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-16 14:17:01 +01:00
dependabot[bot] e2700bf391 build(deps): bump go-task/setup-task from 2.0.0 to 2.1.0 (#7468)
Bumps [go-task/setup-task](https://github.com/go-task/setup-task) from
2.0.0 to 2.1.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/go-task/setup-task/releases">go-task/setup-task's
releases</a>.</em></p>
<blockquote>
<h2>v2.1.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Replaced <code>typed-rest-client</code> with
<code>@actions/http-client</code> for GitHub API calls
to eliminate the Node 24 <code>DEP0169</code> deprecation warning about
<code>url.parse()</code> (<a
href="https://redirect.github.com/go-task/setup-task/issues/5">#5</a> by
<a href="https://github.com/vmaerten"><code>@​vmaerten</code></a>).</li>
<li>Modernized the TypeScript tooling stack (vitest, oxlint,
<code>@actions/core@2</code>,
<code>@actions/io@2</code>, updated <code>@types/node</code>,
<code>@vercel/ncc</code>, <code>prettier</code>, etc.) (<a
href="https://redirect.github.com/go-task/setup-task/issues/5">#5</a> by
<a href="https://github.com/vmaerten"><code>@​vmaerten</code></a>).</li>
<li>Migrated the project to ESM (sources + bundle). Aligns with the new
<code>@actions/*</code> ESM-only majors and produces a ~47% smaller
<code>dist/index.js</code> (<a
href="https://redirect.github.com/go-task/setup-task/issues/5">#5</a> by
<a href="https://github.com/vmaerten"><code>@​vmaerten</code></a>).</li>
<li>Upgraded <code>@actions/core</code> 2 → 3,
<code>@actions/http-client</code> 2 → 4,
<code>@actions/io</code> 2 → 3, <code>@actions/tool-cache</code> 2 → 4,
<code>typescript</code> 5 → 6, and
<code>markdownlint-cli</code> 0.47 → 0.48 (<a
href="https://redirect.github.com/go-task/setup-task/issues/5">#5</a> by
<a href="https://github.com/vmaerten"><code>@​vmaerten</code></a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/go-task/setup-task/blob/main/CHANGELOG.md">go-task/setup-task's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>Unreleased</h2>
<h2>v2.2.0 - 2026-08-09</h2>
<ul>
<li>Added a <code>checksum</code> input to verify the SHA256 digest of
the downloaded Task
archive (<a
href="https://redirect.github.com/go-task/setup-task/pull/16">#16</a> by
<a href="https://github.com/illera88"><code>@​illera88</code></a>).</li>
<li>Updated development dependencies: <code>typescript</code> 6 → 7,
<code>@vercel/ncc</code> 0.38 →
0.44, <code>markdownlint-cli</code> 0.48 → 0.49, and <code>oxfmt</code>
0.50 → 0.62
(<a
href="https://redirect.github.com/go-task/setup-task/pull/15">#15</a>).</li>
</ul>
<h2>v2.1.0 - 2026-05-17</h2>
<ul>
<li>Replaced <code>typed-rest-client</code> with
<code>@actions/http-client</code> for GitHub API calls
to eliminate the Node 24 <code>DEP0169</code> deprecation warning about
<code>url.parse()</code>.</li>
<li>Modernized the TypeScript tooling stack (vitest, oxlint,
<code>@actions/core@2</code>,
<code>@actions/io@2</code>, updated <code>@types/node</code>,
<code>@vercel/ncc</code>, <code>prettier</code>, etc.).</li>
<li>Migrated the project to ESM (sources + bundle). Aligns with the new
<code>@actions/*</code> ESM-only majors and produces a ~47% smaller
<code>dist/index.js</code>.</li>
<li>Upgraded <code>@actions/core</code> 2 → 3,
<code>@actions/http-client</code> 2 → 4,
<code>@actions/io</code> 2 → 3, <code>@actions/tool-cache</code> 2 → 4,
<code>typescript</code> 5 → 6, and
<code>markdownlint-cli</code> 0.47 → 0.48.</li>
</ul>
<h2>v2.0.0 - 2026-03-18</h2>
<ul>
<li><strong>BREAKING</strong>: Upgraded to Node 24. Requires a GitHub
Actions runner with
Node.js 24 support
(<a
href="https://redirect.github.com/go-task/setup-task/pull/10">#10</a> by
<a href="https://github.com/vmaerten"><code>@​vmaerten</code></a>).</li>
</ul>
<h2>v1.1.0 - 2026-03-17</h2>
<ul>
<li>Added configurable HTTP retry for API requests
(<a href="https://redirect.github.com/go-task/setup-task/pull/7">#7</a>
by <a
href="https://github.com/vmaerten"><code>@​vmaerten</code></a>).</li>
</ul>
<h2>v1.0.0 - 2025-09-12</h2>
<ul>
<li>Forked <a
href="https://github.com/arduino/setup-task">arduino/setup-task</a> (by
<a href="https://github.com/pd93"><code>@​pd93</code></a>).</li>
<li>Default <code>repo-token</code> to <code>{{github.token}}</code>
(<a
href="https://redirect.github.com/arduino/setup-task/pull/642">arduino/setup-task#642</a>
by
<a href="https://github.com/shrink"><code>@​shrink</code></a>).</li>
<li>Fixed a bug where the action would fail is Task pushed a tag without
a release
(<a
href="https://redirect.github.com/arduino/setup-task/pull/490">arduino/setup-task#490</a>,
<a
href="https://redirect.github.com/arduino/setup-task/pull/1193">arduino/setup-task#1193</a>
by
<a href="https://github.com/trim21"><code>@​trim21</code></a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/go-task/setup-task/commit/54fb0b2f4e4f3b1c1e1c0b2050ab8f28d794ca5f"><code>54fb0b2</code></a>
fix test</li>
<li><a
href="https://github.com/go-task/setup-task/commit/a3ce905e2b21a672e0f8b0109be2a38c5ffbd461"><code>a3ce905</code></a>
fix test</li>
<li><a
href="https://github.com/go-task/setup-task/commit/533258619790ff7048a24bc82b7d097ffbfbde10"><code>5332586</code></a>
fix test</li>
<li><a
href="https://github.com/go-task/setup-task/commit/7d25465fe82d5ceaa018b136e5117b6f8cd37eb2"><code>7d25465</code></a>
fix test</li>
<li><a
href="https://github.com/go-task/setup-task/commit/7a0f90a08f2d4d3a3bb462b3e976240197d8925d"><code>7a0f90a</code></a>
fix: fetch versions from release instead of tags</li>
<li>See full diff in <a
href="https://github.com/go-task/setup-task/compare/v2...01a4adf9db2d14c1de7a560f09170b6e0df736aa">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-16 14:16:50 +01:00
dependabot[bot] eece9fbdf8 build(deps): bump commons-net:commons-net from 3.11.1 to 3.13.0 (#7517)
Bumps [commons-net:commons-net](https://github.com/apache/commons-net)
from 3.11.1 to 3.13.0.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/apache/commons-net/blob/master/RELEASE-NOTES.txt">commons-net:commons-net's
changelog</a>.</em></p>
<blockquote>
<h2>Apache Commons Net 3.13.0 Release Notes</h2>
<p>The Apache Commons Net team is pleased to announce the release of
Apache Commons Net 3.13.0.</p>
<p>Apache Commons Net library contains a collection of network utilities
and protocol implementations.
Supported protocols include Echo, Finger, FTP, NNTP, NTP, POP3(S),
SMTP(S), Telnet, and Whois.</p>
<p>This is a feature and maintenance release. Java 8 or later is
required.</p>
<p>For complete information on Apache Commons Net, including
instructions on how to submit bug reports,
patches, or suggestions for improvement, see the Apache Commons Net
website:</p>
<p><a
href="https://commons.apache.org/proper/commons-net/">https://commons.apache.org/proper/commons-net/</a></p>
<p>Download page: <a
href="https://commons.apache.org/proper/commons-net/download_net.cgi">https://commons.apache.org/proper/commons-net/download_net.cgi</a></p>
<h2>New features</h2>
<p>o Add DatagramSocketClient.getDefaultTimeoutDuration() and deprecate
getDefaultTimeout(). Thanks to Gary Gregory.
o NET-741: Add subnet IPv6 handling with SubnetUtils6 <a
href="https://redirect.github.com/apache/commons-net/issues/391">#391</a>.
Thanks to Maros Orsak, Gary Gregory.</p>
<h2>Fixed Bugs</h2>
<p>o DaytimeTCPClientTest now should now pass inside most VPNs. Thanks
to Henri Biestro, Gary Gregory.
o Migrate tests to JUnit5 <a
href="https://redirect.github.com/apache/commons-net/issues/358">#358</a>,
<a
href="https://redirect.github.com/apache/commons-net/issues/359">#359</a>.
Thanks to Jakub Kupczyk, Gary Gregory.
o           Fix malformed Javadoc comments. Thanks to Gary Gregory.
o IMAPExportMbox now restores the current thread's interrupt flag when
catching InterruptedException. Thanks to Gary Gregory.
o IOUtil.readWrite() now restores the current thread's interrupt flag
when catching InterruptedException. Thanks to Gary Gregory.
o TelnetInputStream now restores the current thread's interrupt flag
when catching InterruptedException. Thanks to Gary Gregory.
o NET-740: FTP fails to parse listings for Linux vsftpd in Chinese or
Japanese <a
href="https://redirect.github.com/apache/commons-net/issues/393">#393</a>.
Thanks to Jianwei Guo, Gary Gregory.
o TelnetInputStream.read() doesn't preserve the original
InterruptedException as the cause of its InterruptedIOException. Thanks
to Gary Gregory.
o FTPClient._storeFile(String, String, InputStream) doesn't always close
it's internal socket when an exception is thrown early in processing.
Thanks to Gary Gregory.
o ListenerList.removeListener(T) now ignores null input to avoid a
NullPointerException. Thanks to Gary Gregory.
o ListenerList.addListener(T) now ignores null input. Thanks to Gary
Gregory.
o Fix typo in FTPConnectionClosedException message from
FTP.getReply(boolean). Thanks to Gary Gregory.
o Reimplement Util.copyReader() with IOUtils.copyLarge(). Thanks to Gary
Gregory.
o Reimplement Util.copyStream() with IOUtils.copyLarge(). Thanks to Gary
Gregory.
o Reimplement Util.copyStream() with IOUtils.copyLarge(). Thanks to Gary
Gregory.
o Deprecate Util.copyReader(Reader, Writer) in favor of
IOUtils.copyLarge(Reader, Writer). Thanks to Gary Gregory.</p>
<h2>Changes</h2>
<p>o Bump org.apache.commons:commons-parent from 85 to 97 <a
href="https://redirect.github.com/apache/commons-net/issues/371">#371</a>,
<a
href="https://redirect.github.com/apache/commons-net/issues/388">#388</a>,
<a
href="https://redirect.github.com/apache/commons-net/issues/389">#389</a>.
Thanks to Gary Gregory, Dependabot.
o Bump org.apache.commons:commons-lang3 from 3.18.0 to 3.19.0. Thanks to
Gary Gregory, Dependabot.</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/apache/commons-net/commit/35fa0dff3e8660359832e72631ff3cf557b543a8"><code>35fa0df</code></a>
Prepare for the release candidate 3.13.0 RC1</li>
<li><a
href="https://github.com/apache/commons-net/commit/1cc5b6e77d86bb54131742fee175e7c700bbf2ca"><code>1cc5b6e</code></a>
Prepare for the next release candidate</li>
<li><a
href="https://github.com/apache/commons-net/commit/a4b246e085c1d3ce5936806d8f85d2e826daca8a"><code>a4b246e</code></a>
Reuse IOUtils.copyLarge()</li>
<li><a
href="https://github.com/apache/commons-net/commit/e5ca262cea38b2f889eb003818cf9f97a6395ad6"><code>e5ca262</code></a>
Javadoc</li>
<li><a
href="https://github.com/apache/commons-net/commit/3cb18ec82ef0b3640536137639763a682994a61e"><code>3cb18ec</code></a>
Deprecate Util.copyReader(Reader, Writer) in favor of</li>
<li><a
href="https://github.com/apache/commons-net/commit/3373d77db2376665a034e753e3646251b3d23aa4"><code>3373d77</code></a>
Reimplement Util.copyReader() with IOUtils.copyLarge()</li>
<li><a
href="https://github.com/apache/commons-net/commit/3be2077ccf1da69614a7883ed352037e30f3366f"><code>3be2077</code></a>
Reimplement Util.copyStream() with IOUtils.copyLarge()</li>
<li><a
href="https://github.com/apache/commons-net/commit/7b41144008427acceea596ac74f36e716ea2586e"><code>7b41144</code></a>
Javadoc</li>
<li><a
href="https://github.com/apache/commons-net/commit/1b584b493ffa6a1b42cbda3c7f683e073f1a3bdf"><code>1b584b4</code></a>
Fix typo in FTPConnectionClosedException message from</li>
<li><a
href="https://github.com/apache/commons-net/commit/3792023843b95dabe60fb745287cae94c8a44be5"><code>3792023</code></a>
Javadoc</li>
<li>Additional commits viewable in <a
href="https://github.com/apache/commons-net/compare/rel/commons-net-3.11.1...rel/commons-net-3.13.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=commons-net:commons-net&package-manager=gradle&previous-version=3.11.1&new-version=3.13.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-08-16 14:16:32 +01:00
dependabot[bot] fb362f70ab build(deps): bump github/codeql-action/upload-sarif from 4.37.4 to 4.37.6 (#7467)
Bumps
[github/codeql-action/upload-sarif](https://github.com/github/codeql-action)
from 4.37.4 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/upload-sarif'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>
</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/upload-sarif'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>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>
<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/f205ea1c3313d32999d8d6a48b4f6530d4437b38...5595ccaf912efad79be6eef63a5619ff05969be3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action/upload-sarif&package-manager=github_actions&previous-version=4.37.4&new-version=4.37.6)](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-16 14:16:15 +01:00
dependabot[bot] 88afdd07af build(deps): bump awsSdkVersion from 2.44.12 to 2.51.2 (#7371)
Bumps `awsSdkVersion` from 2.44.12 to 2.51.2.
Updates `software.amazon.awssdk:s3` from 2.44.12 to 2.51.2

Updates `software.amazon.awssdk:url-connection-client` from 2.44.12 to
2.51.2

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-16 14:15:58 +01:00
dependabot[bot] 3bb3b721de build(deps): bump logback from 1.5.32 to 1.6.1 (#7339)
Bumps `logback` from 1.5.32 to 1.6.1.
Updates `ch.qos.logback:logback-core` from 1.5.32 to 1.6.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/qos-ch/logback/releases">ch.qos.logback:logback-core's
releases</a>.</em></p>
<blockquote>
<h2>Logback 1.6.1</h2>
<p><strong>2026-07-28 Release of logback version 1.6.1</strong></p>
<p>• In TimeBasedRollingPolicy, when the file option is set, the
intermediate file renamed before asynchronous compression now receives
the target archive name without the compression suffix (e.g.
<code>.gz</code>, <code>.zip</code>, <code>.xz</code>). Previously it
used a nanotime-based <code>.tmp</code> suffix. This makes the file
easier to identify if compression fails during rollover. (See also the
following paragraph.)</p>
<p>• On GZ, ZIP, or XZ compression failure, the original (uncompressed)
log file is no longer deleted. Compression strategies now delete the
source file only after successful compression and emit a warning that
the original was left intact.</p>
<p>• ConsoleAppender with <!-- raw HTML omitted --> now probes JLine's
org.jline.jansi.AnsiConsole first and falls back to the legacy
FuseSource org.fusesource.jansi.AnsiConsole class. This keeps ANSI
coloring working after Jansi moved under the JLine project. The optional
org.jline:jansi-core artifact is declared as a dependency alongside the
existing FuseSource jansi dependency. A preferredJansiClassName property
was added for tests. This issue was reported in <a
href="https://redirect.github.com/qos-ch/logback/issues/1043">issues/1043</a>
by <a href="https://github.com/seonwooj0810">seonwoo_jung</a> who also
provided the relevant PR.</p>
<p>• LayoutWrappingEncoder now reports an error at start() when no
layout is set and guards encode() against a null layout. Previously, a
missing layout (for example after an ignored <!-- raw HTML omitted
-->/<!-- raw HTML omitted -->/<!-- raw HTML omitted --> branch) allowed
the encoder to start and then fail with a NullPointerException on every
event, resulting in silent log loss. This issue was reported in <a
href="https://redirect.github.com/qos-ch/logback/issues/1046">issues/1046</a>
by <a href="https://github.com/seonwooj0810">seonwoo_jung</a> who also
provided the relevant PR.</p>
<p>• FileCollisionAnalyser now detects file collisions involving nested
appenders of SiftingAppender. When the nested file or fileNamePattern
does not textually reference the discriminator key (e.g. ${userId}), a
warning is issued at configuration time naming the appender, the key,
and the shared target. This closes a gap where statically declared file
appenders were checked but sifted nested appenders were not. This
enhancement was contributed in [PR <a
href="https://redirect.github.com/qos-ch/logback/issues/1041">#1041</a>](<a
href="https://redirect.github.com/qos-ch/logback/issues/1041">qos-ch/logback#1041</a>)
by <a href="https://github.com/seonwooj0810">seonwoo_jung</a>.</p>
<p>• More defensive handling in SyslogOutputStream and
SyslogAppenderBase: the close() method now ensures that resources are
closed, writes and flushes check that the underlying resources are in a
valid state and fallback to no-op otherwise.</p>
<p>• A bit-wise identical binary of this version can be reproduced by
building from source code at commit
57759f433000a133088ef0441038963134437fbd associated with the tag
v_1.6.1. The release was built using Java &quot;21&quot; 2023-10-17 LTS
build 21.0.1.+12-LTS-29 under Linux Debian 11.6.</p>
<p>• See <a
href="https://logback.qos.ch/news.html#1.6.1">https://logback.qos.ch/news.html#1.6.1</a>
for the original text.</p>
<h2>Logback 1.6.0</h2>
<p><strong>2026-07-23 Release of logback version 1.6.0</strong></p>
<p>• Removed certain deprecated variables, methods, and classes. For the
list of removed members see <a
href="https://logback.qos.ch/notes/release_1.6.0.txt">release_1.6.0.txt.</a></p>
<p>• In <code>AsyncAppenderBase</code>, the
<code>put(ILoggingEvent)</code> method now has the protected modifier to
allow access from derived classes. This change was requested by Thomas
Skjølberg in <a
href="https://redirect.github.com/qos-ch/logback/pull/1053">pr#1053</a>.</p>
<p>• Bump SLF4J dependency to version 2.0.18.</p>
<p>• <strong>See also the overview of the <a
href="https://logback.qos.ch/news.html#latest_stable">1.6.x
series</a>.</strong></p>
<p>• A bit-wise identical binary of this version can be reproduced by
building from source code at commit
b07adf36019b51a10f824fdd94009985c587b1d3 associated with the tag
v_1.6.0. The release was built using Java &quot;21&quot; 2023-10-17 LTS
build 21.0.1.+12-LTS-29 under Linux Debian 11.6.</p>
<h2>Logback 1.5.38</h2>
<p><strong>2026-07-09 Release of logback version 1.5.38</strong></p>
<p>• In <code>HardenedObjectInputStream</code>, fixed a typo preventing
<code>Throwable</code> objects from being white-filtered. This issue was
reported in [PR <a
href="https://redirect.github.com/qos-ch/logback/issues/1045">#1045</a>](<a
href="https://redirect.github.com/qos-ch/logback/pull/1045">qos-ch/logback#1045</a>)
by <a href="https://github.com/t0rchwo0d">t0rchwo0d</a>.</p>
<p>• A bitwise identical binary of this version can be reproduced by
building from source code at commit
d04984a41fce42977466f45a2f076f0ee5cc4207 associated with the tag
v_1.5.38. Release built using Java &quot;21&quot; 2023-10-17 LTS build
21.0.1.+12-LTS-29 under Linux Debian 11.6.</p>
<h2>Logback 1.5.37</h2>
<p><strong>2026-06-26 Release of logback version 1.5.37</strong></p>
<ol>
<li>• Given the numerous vulnerabilities related to conditional
configuration processing based on the evaluation of Java expressions
using the Janino library, support for such expressions has been removed.
Users are offered the an <a
href="https://logback.qos.ch/translator/services/conditionalConfigMigrator.html">online
migration service</a> or the <code>&lt;condition&gt;</code> element
introduced in version 1.5.20. See the <a
href="https://logback.qos.ch/manual/configuration.html#conditional">relevant
documentation</a> for more details.</li>
</ol>
<p>• A bitwise identical binary of this version can be reproduced by
building from source code at commit
c1df7f522e648eec7b4ef6a12c8758fec0f00048 associated with the tag
v_1.5.37. Release built using Java &quot;21&quot; 2023-10-17 LTS build
21.0.1.+12-LTS-29 under Linux Debian 11.6.</p>
<h2>Logback 1.5.36</h2>
<p><strong>2026-06-25 Release of logback version 1.5.36</strong></p>
<p>• The 'condition' attribute in <code>&lt;if&gt;</code> elements now
reject certain references that are associated with ACE attacks. This
issue was reported by &quot;yulate&quot; (<a
href="mailto:yulate531@gmail.com.com">yulate531@gmail.com.com</a>) and
registered as <a
href="https://www.cve.org/cverecord?id=CVE-2026-13006">CVE-2026-13006</a>.
<strong>Please note that version 1.5.37 provides the full fix to this
vulnerability.</strong></p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/qos-ch/logback/commit/57759f433000a133088ef0441038963134437fbd"><code>57759f4</code></a>
prepare release 1.6.1</li>
<li><a
href="https://github.com/qos-ch/logback/commit/175f99f2093ae07f4c8d44f93b800f65b03a6e19"><code>175f99f</code></a>
fix imports</li>
<li><a
href="https://github.com/qos-ch/logback/commit/4b8773ed127fdc62b85a7c7ddaef10f25830788c"><code>4b8773e</code></a>
add compressionFailureLeavesOriginalFileIntact test for XZ
compression</li>
<li><a
href="https://github.com/qos-ch/logback/commit/cafaf1115fd20be19b2f7a4a09184446e8bbc04d"><code>cafaf11</code></a>
do not delete original file if compression fails</li>
<li><a
href="https://github.com/qos-ch/logback/commit/ee50125b293f5a731543de9f9f5fb72756a43464"><code>ee50125</code></a>
let the temporary file before compression be target file without the .gz
or ....</li>
<li><a
href="https://github.com/qos-ch/logback/commit/5626acc301f4039537a6c668f0f2d51989472785"><code>5626acc</code></a>
minor refactoring</li>
<li><a
href="https://github.com/qos-ch/logback/commit/d97da4fbc0de00ca901ef78d91b9fc1850ae803f"><code>d97da4f</code></a>
minor refactoring</li>
<li><a
href="https://github.com/qos-ch/logback/commit/159c045d8f045ccf8b382775d81d83919c228cca"><code>159c045</code></a>
more defensive coding in SyslogOutputStream and in
SyslogAppenderBase</li>
<li><a
href="https://github.com/qos-ch/logback/commit/9427d6b23d5a692c8a76a994c076ef68ded7835c"><code>9427d6b</code></a>
slight refactoring for clarity</li>
<li><a
href="https://github.com/qos-ch/logback/commit/79c4179c0b440a9dcf35bc9bda1ead2b2f90966c"><code>79c4179</code></a>
slight refactoring</li>
<li>Additional commits viewable in <a
href="https://github.com/qos-ch/logback/compare/v_1.5.32...v_1.6.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `ch.qos.logback:logback-classic` from 1.5.32 to 1.6.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/qos-ch/logback/releases">ch.qos.logback:logback-classic's
releases</a>.</em></p>
<blockquote>
<h2>Logback 1.6.1</h2>
<p><strong>2026-07-28 Release of logback version 1.6.1</strong></p>
<p>• In TimeBasedRollingPolicy, when the file option is set, the
intermediate file renamed before asynchronous compression now receives
the target archive name without the compression suffix (e.g.
<code>.gz</code>, <code>.zip</code>, <code>.xz</code>). Previously it
used a nanotime-based <code>.tmp</code> suffix. This makes the file
easier to identify if compression fails during rollover. (See also the
following paragraph.)</p>
<p>• On GZ, ZIP, or XZ compression failure, the original (uncompressed)
log file is no longer deleted. Compression strategies now delete the
source file only after successful compression and emit a warning that
the original was left intact.</p>
<p>• ConsoleAppender with <!-- raw HTML omitted --> now probes JLine's
org.jline.jansi.AnsiConsole first and falls back to the legacy
FuseSource org.fusesource.jansi.AnsiConsole class. This keeps ANSI
coloring working after Jansi moved under the JLine project. The optional
org.jline:jansi-core artifact is declared as a dependency alongside the
existing FuseSource jansi dependency. A preferredJansiClassName property
was added for tests. This issue was reported in <a
href="https://redirect.github.com/qos-ch/logback/issues/1043">issues/1043</a>
by <a href="https://github.com/seonwooj0810">seonwoo_jung</a> who also
provided the relevant PR.</p>
<p>• LayoutWrappingEncoder now reports an error at start() when no
layout is set and guards encode() against a null layout. Previously, a
missing layout (for example after an ignored <!-- raw HTML omitted
-->/<!-- raw HTML omitted -->/<!-- raw HTML omitted --> branch) allowed
the encoder to start and then fail with a NullPointerException on every
event, resulting in silent log loss. This issue was reported in <a
href="https://redirect.github.com/qos-ch/logback/issues/1046">issues/1046</a>
by <a href="https://github.com/seonwooj0810">seonwoo_jung</a> who also
provided the relevant PR.</p>
<p>• FileCollisionAnalyser now detects file collisions involving nested
appenders of SiftingAppender. When the nested file or fileNamePattern
does not textually reference the discriminator key (e.g. ${userId}), a
warning is issued at configuration time naming the appender, the key,
and the shared target. This closes a gap where statically declared file
appenders were checked but sifted nested appenders were not. This
enhancement was contributed in [PR <a
href="https://redirect.github.com/qos-ch/logback/issues/1041">#1041</a>](<a
href="https://redirect.github.com/qos-ch/logback/issues/1041">qos-ch/logback#1041</a>)
by <a href="https://github.com/seonwooj0810">seonwoo_jung</a>.</p>
<p>• More defensive handling in SyslogOutputStream and
SyslogAppenderBase: the close() method now ensures that resources are
closed, writes and flushes check that the underlying resources are in a
valid state and fallback to no-op otherwise.</p>
<p>• A bit-wise identical binary of this version can be reproduced by
building from source code at commit
57759f433000a133088ef0441038963134437fbd associated with the tag
v_1.6.1. The release was built using Java &quot;21&quot; 2023-10-17 LTS
build 21.0.1.+12-LTS-29 under Linux Debian 11.6.</p>
<p>• See <a
href="https://logback.qos.ch/news.html#1.6.1">https://logback.qos.ch/news.html#1.6.1</a>
for the original text.</p>
<h2>Logback 1.6.0</h2>
<p><strong>2026-07-23 Release of logback version 1.6.0</strong></p>
<p>• Removed certain deprecated variables, methods, and classes. For the
list of removed members see <a
href="https://logback.qos.ch/notes/release_1.6.0.txt">release_1.6.0.txt.</a></p>
<p>• In <code>AsyncAppenderBase</code>, the
<code>put(ILoggingEvent)</code> method now has the protected modifier to
allow access from derived classes. This change was requested by Thomas
Skjølberg in <a
href="https://redirect.github.com/qos-ch/logback/pull/1053">pr#1053</a>.</p>
<p>• Bump SLF4J dependency to version 2.0.18.</p>
<p>• <strong>See also the overview of the <a
href="https://logback.qos.ch/news.html#latest_stable">1.6.x
series</a>.</strong></p>
<p>• A bit-wise identical binary of this version can be reproduced by
building from source code at commit
b07adf36019b51a10f824fdd94009985c587b1d3 associated with the tag
v_1.6.0. The release was built using Java &quot;21&quot; 2023-10-17 LTS
build 21.0.1.+12-LTS-29 under Linux Debian 11.6.</p>
<h2>Logback 1.5.38</h2>
<p><strong>2026-07-09 Release of logback version 1.5.38</strong></p>
<p>• In <code>HardenedObjectInputStream</code>, fixed a typo preventing
<code>Throwable</code> objects from being white-filtered. This issue was
reported in [PR <a
href="https://redirect.github.com/qos-ch/logback/issues/1045">#1045</a>](<a
href="https://redirect.github.com/qos-ch/logback/pull/1045">qos-ch/logback#1045</a>)
by <a href="https://github.com/t0rchwo0d">t0rchwo0d</a>.</p>
<p>• A bitwise identical binary of this version can be reproduced by
building from source code at commit
d04984a41fce42977466f45a2f076f0ee5cc4207 associated with the tag
v_1.5.38. Release built using Java &quot;21&quot; 2023-10-17 LTS build
21.0.1.+12-LTS-29 under Linux Debian 11.6.</p>
<h2>Logback 1.5.37</h2>
<p><strong>2026-06-26 Release of logback version 1.5.37</strong></p>
<ol>
<li>• Given the numerous vulnerabilities related to conditional
configuration processing based on the evaluation of Java expressions
using the Janino library, support for such expressions has been removed.
Users are offered the an <a
href="https://logback.qos.ch/translator/services/conditionalConfigMigrator.html">online
migration service</a> or the <code>&lt;condition&gt;</code> element
introduced in version 1.5.20. See the <a
href="https://logback.qos.ch/manual/configuration.html#conditional">relevant
documentation</a> for more details.</li>
</ol>
<p>• A bitwise identical binary of this version can be reproduced by
building from source code at commit
c1df7f522e648eec7b4ef6a12c8758fec0f00048 associated with the tag
v_1.5.37. Release built using Java &quot;21&quot; 2023-10-17 LTS build
21.0.1.+12-LTS-29 under Linux Debian 11.6.</p>
<h2>Logback 1.5.36</h2>
<p><strong>2026-06-25 Release of logback version 1.5.36</strong></p>
<p>• The 'condition' attribute in <code>&lt;if&gt;</code> elements now
reject certain references that are associated with ACE attacks. This
issue was reported by &quot;yulate&quot; (<a
href="mailto:yulate531@gmail.com.com">yulate531@gmail.com.com</a>) and
registered as <a
href="https://www.cve.org/cverecord?id=CVE-2026-13006">CVE-2026-13006</a>.
<strong>Please note that version 1.5.37 provides the full fix to this
vulnerability.</strong></p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/qos-ch/logback/commit/57759f433000a133088ef0441038963134437fbd"><code>57759f4</code></a>
prepare release 1.6.1</li>
<li><a
href="https://github.com/qos-ch/logback/commit/175f99f2093ae07f4c8d44f93b800f65b03a6e19"><code>175f99f</code></a>
fix imports</li>
<li><a
href="https://github.com/qos-ch/logback/commit/4b8773ed127fdc62b85a7c7ddaef10f25830788c"><code>4b8773e</code></a>
add compressionFailureLeavesOriginalFileIntact test for XZ
compression</li>
<li><a
href="https://github.com/qos-ch/logback/commit/cafaf1115fd20be19b2f7a4a09184446e8bbc04d"><code>cafaf11</code></a>
do not delete original file if compression fails</li>
<li><a
href="https://github.com/qos-ch/logback/commit/ee50125b293f5a731543de9f9f5fb72756a43464"><code>ee50125</code></a>
let the temporary file before compression be target file without the .gz
or ....</li>
<li><a
href="https://github.com/qos-ch/logback/commit/5626acc301f4039537a6c668f0f2d51989472785"><code>5626acc</code></a>
minor refactoring</li>
<li><a
href="https://github.com/qos-ch/logback/commit/d97da4fbc0de00ca901ef78d91b9fc1850ae803f"><code>d97da4f</code></a>
minor refactoring</li>
<li><a
href="https://github.com/qos-ch/logback/commit/159c045d8f045ccf8b382775d81d83919c228cca"><code>159c045</code></a>
more defensive coding in SyslogOutputStream and in
SyslogAppenderBase</li>
<li><a
href="https://github.com/qos-ch/logback/commit/9427d6b23d5a692c8a76a994c076ef68ded7835c"><code>9427d6b</code></a>
slight refactoring for clarity</li>
<li><a
href="https://github.com/qos-ch/logback/commit/79c4179c0b440a9dcf35bc9bda1ead2b2f90966c"><code>79c4179</code></a>
slight refactoring</li>
<li>Additional commits viewable in <a
href="https://github.com/qos-ch/logback/compare/v_1.5.32...v_1.6.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-16 14:15:34 +01:00
Anthony Stirling 60741e305d Update CONTRIBUTING.md to include license information (#7512)
Added a section about licensing contributions to the project.

# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-16 11:22:19 +01:00
Anthony StirlingandSaul 6143610608 fix(frontend): preserve pdf link targets in desktop viewer (#7235)
Same PR as #6396, just with the conflicts resolved and some fixes on
top. Original commit by @saul1310 is preserved as-is; everything else is
a follow-up commit.

Refs #6272

## Conflicts

#6396 was written before the frontend was restructured, so all four
files it touched moved (`frontend/src/**` -> `frontend/editor/src/**`)
and `LinkLayer.tsx` had drifted. Cherry-picked with rename detection and
re-resolved against current `main`.

## Fixes on top

- **Reuse the existing platform seam instead of adding a second one.**
`main` already has `@app/platform/*` seams with per-flavour
implementations; #6396 added a parallel `@app/utils/openExternalUrl`
core+desktop pair that re-implemented the Tauri shell call already in
`desktop/platform/openExternal.ts`. Split into a pure sanitiser
(`@app/utils/externalUrl`) and a platform seam
(`@app/platform/openExternalTab`), with the desktop impl delegating to
the existing `openExternal`.
- **Kept PDF links off the `openExternal` seam.** That seam is for
leave-and-return redirects (Stripe) and its saas impl is
`window.location.assign` - routing PDF links through it would navigate
the whole app away from the user's document. `openExternalTab` always
opens alongside the app; desktop shadows it to escape the webview.
- **Fixed the same defect in two sibling call sites** that #6396 didn't
cover: `BookmarkSidebar` (bookmark URI / LaunchAppOrOpenFile actions)
and `useAnnotationMenuHandlers` (annotation menu "go to link"). Both
called `window.open` on an unsanitised PDF-supplied URI, so on desktop
they trapped the link in the webview exactly like the viewer did.
- **Dropped the unguarded fallback.** The old code fell back to
`window.open(uri)` when `new URL()` threw, so an unparseable URI
bypassed the allowlist entirely. It is now blocked.
- Empty/whitespace URIs are blocked rather than silently resolving to
the app's own page via the base URL.
- Tests: sanitiser cases (casing, leading whitespace, `data:`,
`vbscript:`, unparseable), a core seam test asserting
new-tab-not-navigate, and a desktop seam regression test asserting the
URL goes to the OS rather than `window.open`.
- **`openExternalTab` now re-validates its own input.** Every caller
sanitises first, so nothing reached it unvalidated - but it is the sink
that hands a URL to `window.open` (executes `javascript:` in our origin)
or to an OS handler on desktop, and its safety shouldn't depend on
callers remembering. Both impls fail closed, with tests that call them
directly with `javascript:`/`data:`/`file:`/`ftp:`.

## Unrelated fix included (flagged deliberately)

The last commit fixes `frontend/editor/vitest.config.ts`: `testTimeout:
10000` was set on the root `test` block, but tests all run under
`projects`, which do not inherit it - so the whole suite has silently
been running at vitest's 5s default.

This is not cosmetic. It made `task check` fail intermittently on
unrelated portal specs (`demoData`, `ConnectionModal`); the ConsignO
test takes 2966ms with only the portal project running, i.e. 59% of a
budget it was never meant to have, so any CPU contention tips it over.
Proven with an identical 6.5s probe test: times out at 5000ms on the old
config, passes at 6512ms on the fixed one.

Happy to split this into its own PR if preferred - it is here because
the gate could not be trusted without it.

## Validation

Typecheck passes for all 7 build flavours (core, proprietary, saas,
desktop, cloud, prototypes, portal); ESLint, Prettier, dpdm and the full
1662-test vitest suite pass.

Driven live against the dev server + backend with a PDF carrying five
URI annotations (https, `javascript:`, mailto, `file:`, relative). 14/14
behavioural checks pass on this branch; 5 of them fail on `main`:

| check | main | this PR |
| --- | --- | --- |
| safe https link exposes real href (copy-link) | `href="#"` |
`https://example.com/safe-link?a=1` |
| link opens in new tab / tabnabbing-proof | no `target`/`rel` |
`_blank` + `noopener noreferrer` |
| mailto link exposes real href | `href="#"` | `mailto:test@example.com`
|
| relative URI resolved against app origin | `href="#"` | resolved |
| `javascript:` / `file:` never reach href | blocked | blocked |
| clicking blocked link doesn't execute or navigate | ok | ok |
| clicking safe link opens new tab at source URL | - | ok, app not
navigated away |

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Testing (if applicable)

- [x] I have run `task check` to verify linters, typechecks, and tests
pass
- [x] I have tested my changes locally

---------

Co-authored-by: Saul <saulifshin.cs@gmail.com>
2026-08-14 18:07:42 +01:00
Anthony Stirling bff1ea916d Add n8n connection preset and workflow step operations (#7447)
# Description of Changes

Add n8n, simple reuse of webhook call etc and custom API  etc etc


<img width="2104" height="1090" alt="image"
src="https://github.com/user-attachments/assets/0d0df39a-31b3-4e1b-ac63-847aefa06ffe"
/>

<img width="2104" height="1592" alt="image"
src="https://github.com/user-attachments/assets/4074cce2-0f4b-4300-813c-d49576b33940"
/>

<img width="1021" height="797" alt="image"
src="https://github.com/user-attachments/assets/370f54f7-3066-4454-9618-9ddd2cb91b83"
/>

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-14 18:07:12 +01:00
EthanHealy01 2483e9f37a Report editor-originated failures into the same queue (Review Flow PR 2) (#7296)
Review Flow PR 2 of 5. Editor tool failures now reach the same durable
queue as failures from folders, buckets and webhooks.

## What's added

**A report endpoint** — `POST /api/v1/file-run-events/reports`, open to
any authenticated user. Takes four fields: `operation`, `errorCode`,
`fileIds`, `detail`. No team, no actor, no filename: the first two come
from the session, the third is never a field. Refused with 400 above 200
file ids, and nothing is written when refused.

**Automatic reporting from every tool** — wired into `useToolOperation`,
so no per-tool work is needed. Client-side refusals (an unsupported
format that never reaches the server) are reported too. User
cancellations are not.

**Error codes parsed from Blob bodies as well as JSON** — a
download-typed tool call fails with a Blob, so `errorCodeOf` handles
both shapes.

**Source attribution for unattended runs** — `sourceId` is threaded from
`PolicyRunner` through `PolicyRun` to the recorded row and out to the
wire, so a folder, bucket or webhook failure names what fed it.
Previously it had none.

**Deleting a file closes its failures** — `FileContext.removeFiles`
notifies `POST /removed-files`, which transitions those incidents to
`FILE_REMOVED`. Terminal, so they leave every reviewer's queue. The rows
stay for audit.

**The queue can be emptied** — reads now default to open statuses only;
ask for a status explicitly to see closed rows.

## Behaviour changes

- **Editor failures dedup per person.** `RecordFailure.scopeRef()`
includes the actor for TOOL-origin rows, so two people hitting the same
failure on the same file are two incidents rather than one. Processor
rows are unaffected and their dedup key is byte-identical to before.
- **`UNKNOWN` offers only Dismiss.** Acknowledge is no longer offered on
it.
- **Background reports no longer raise a toast.** Both calls pass
`suppressErrorToast`, so a failed report is silent as intended;
previously a core build showed the user a "Not Found" toast on every
tool failure.

## What is stored

File ids only, never names. The request type has no filename field, and
a `fileNames` value handed to the client reporter is accepted and
ignored.

One caveat to review deliberately: the free-text `detail` is stored
**verbatim**. `RecordFailure` truncates it at 2000 characters and
nothing else; the redaction that used to strip name-shaped text was
reverted in `024899f3f6` because it made an unclassified failure
impossible to act on. A backend message that embeds a filename
(LibreOffice conversion errors, IO errors) will therefore persist that
text and show it to a team leader.

## How to test

Needs a proprietary or SaaS build with login enabled. `task dev:all`
gives you one.

1. **Report a failure from a tool.** Open a PDF, run **Remove Password**
on it with a wrong password. Nothing visible changes for you: reporting
is silent by design.
2. **See it recorded.** Go to `/processor/documents` and scroll to
**Failures** (dev builds only). A row appears titled "Password-protected
document", with `Hit by <your user>`. Press **Show raw JSON** to see
exactly what was stored.
3. **Confirm no filename is stored as data.** In that JSON, `fileId` is
an opaque uuid and there is no name field. Note the `detail` string may
contain a filename if the backend put one in its message, per the caveat
above.
4. **Confirm the request is capped.** In DevTools, POST to
`/api/v1/file-run-events/reports` with 201 entries in `fileIds`. It
returns 400 naming the limit, and no rows are added.
5. **Deleting a file clears its failure.** Back in the editor, delete
the file you just failed on. Refresh the failures list: its row is gone
from the default view. Filter by `FILE_REMOVED` to see it still exists.
6. **Two people, two incidents.** Have a colleague fail the same tool on
their own copy of the same file. Two rows, not one occurrence count.

## Migration

`source_id` is a new column and `FILE_REMOVED` a new status value. Both
are already in the SaaS migration ([Stirling-PDF-SaaS
#322](https://github.com/Stirling-Tools/Stirling-PDF-SaaS/pull/322));
self-hosted picks them up from `ddl-auto`.
2026-08-14 13:24:41 +00:00
James Brunton 6f2b829f72 Filter Pipelines page to only show what the user thinks as pipelines (#7495)
# Description of Changes
Currently, the Pipelines page shows all backend Policies, which was the
desired behaviour when we first designed this, but as it's come along,
it doesn't feel right anymore. This adds a filter so the Pipelines table
only shows things that have been defined by the user as a New Pipeline,
so not Policies etc.

## Before
<img width="1510" height="789" alt="image"
src="https://github.com/user-attachments/assets/5aebb065-3d42-4483-be3c-253fd8918d49"
/>

## After
<img width="1512" height="790" alt="image"
src="https://github.com/user-attachments/assets/2966a0de-ec9f-43e6-bb1d-c4aeb30dfbf8"
/>
2026-08-14 13:11:02 +00:00
James Brunton 588afb6306 Fix a11y violations in nightlies and improve a11y config (#7499)
# Description of Changes
This fixes the a11y violations that are currently failing in the
nightlies in dark mode. Now that we're down to 0 baseline, we can
require the a11y tests to pass in PRs before they merge, so I've changed
that, and I've also made it so that the nightly will report failures in
both light and dark mode instead of just light mode if that fails.

<img width="2560" height="838" alt="image"
src="https://github.com/user-attachments/assets/b0322182-f6ff-4dea-9a1c-5a6c9c9c5439"
/>
2026-08-14 14:02:16 +01:00
Anthony Stirling 0be10b2dff Cucumber concurrency validation plus fix (#7379)
# Description of Changes

cucumber tests to run multiple threads of commands at same time 

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-14 14:01:45 +01:00
EthanHealy01 55087313b7 Processor UI snags: fat CTAs, real Infrastructure tabs, one surface style (#7497)
Five unrelated snags in the processor (portal) UI, plus fixes they
turned up. No backend changes.

`84 files changed, +892 / −3364`

## Fat CTA buttons

- New `fat` prop on the SUI `Button`: 2.75rem tall, 1.25rem side
padding, 0.75rem corners, semibold. Composes with all four
variants/accents.
- Applied to the page-header CTA on Sources, Documents, Pipelines, Users
(both), Usage, Integrations, Infrastructure — 8 buttons, all in line
with a page title. Nothing else.
- `LandingActions` migrated onto the prop; `.landing-btn-primary` /
`.landing-btn-secondary` and their four `!important`s deleted. The
editor landing CTAs come down 4px with everything else.
- Infrastructure's header CTA is now primary; its "Create key" dropped
to secondary so they stop competing.

<!--IMG:buttons-->

## Documents empty state

- "Connect a source" opened the Sources *page*; it now opens the
`SourceModal` connect flow in place, no route change.
- No extra cache wiring: `SourceModal` already invalidates the sources
query.

<!--IMG:documents-->

## Infrastructure tabs

- Only API Keys and Audit Logs hit real endpoints. Deployments,
Security, Models and Storage read mock-only `/v1/infrastructure/*` that
no backend serves.
- Those four are now disabled: native `disabled`, out of the keyboard
tab order, `aria-disabled`, with the view refusing non-enabled keys as a
second guard.
- Real tabs moved leftmost; API Keys is the default; `?tab=` deep links
validated against the enabled set (the home flow's audit link still
works).
- Deleted: 4 tab components, their fetch fns and ~25 dead types, MSW
handlers, fixtures (908 → 253 lines), dead CSS, unused formatters, 240
lines of `en-US` strings. Most of the −3364.
- Page subtitle no longer advertises the disabled tabs.

<!--IMG:infrastructure-->

## Surface consolidation

- New `Surface` primitive (`sui-surface`): fill, hairline, radius, no
shadow. Kept separate from `sui-nav-surface` so nav chrome can diverge
later.
- `Card` composes it and no longer draws its own shadow — this changes
editor Card usages too, by design.
- SUI primitives that are surfaces adopt it: `MetricCard`,
`MetricStrip`, `NodeCard`, `Table`, `Collapsible`, `CodeBlock`.
- The portal gets its own `.portal-surface` with the same three
declarations, applied to 19 elements. A `sui-` class belongs to the
component that emits it, so feature markup doesn't wear one.
- `raised` variant = one subtle shadow for a surface in front of another
surface (the flow diagram's tiles). Same fill as its parent, so nesting
never shifts a region's colour. Dark has its own value.
- Floating chrome (modals, drawers, dropdowns, assistant, sidebar) keeps
its elevation; sunken wells stay sunken.

<!--IMG:surfaces-->

## Sources list

- Centred "No sources connected yet" empty state removed — it duplicated
the header CTA and pushed the table down the page. The header's "Connect
source" is the single way in.

## Drive-by fixes

- The connect flow rendered unstyled outside the Sources view:
`.portal-conn-picker__*` / `.portal-sources__connection-*` lived in
`views/Sources.css`, which none of the five components rendering them
imported. Moved to `components/sources/connections.css`.
- Three inert custom properties (`--surface-input`, `--color-border-2`,
`--text-default`) are defined nowhere in the codebase —
`.portal-conn-picker__card` had no fill at all as a result.
- Dead CSS removed from `Sources.css` (grep-verified unused): old
expanded-row panel + its keyframes, type-card block.

## Testing

- `task frontend:check` — typecheck, lint (oxlint + 4 theme-lint passes
+ stylelint), format, 238 files / 2063 tests.
- `frontend:typecheck:all` across all 9 tsconfigs.
- `frontend:storybook:a11y:changed` — 119 stories, light and dark, zero
violations, no regressions vs baseline.
- New tests: `Infrastructure.test.tsx` (tab order, default, disabled
behaviour, deep-link filtering) and a Documents test that the
empty-state CTA opens the modal without navigating.
- Merged `origin/main` (#7438 replaced `PipelineHeader` with the new
Create/Edit headers); full suite green at 240 files / 2072 tests after
the merge.
2026-08-14 10:55:15 +00:00
James Brunton 4b26797ad8 Redesign New/Edit Pipeline top bars (#7438)
# Description of Changes
Replace the dev-UI top-bar in the New Pipeline and Edit Pipeline pages
with a redesigned layout appropriate for users. I've got a big list of
extra tweaks I'd like to do to the rest of the page including graph
tweaks etc. but this is the only thing on the New/Edit Pipelines pages
that is blocking for the release.

## Before
### New Pipeline
<img width="1510" height="788" alt="image"
src="https://github.com/user-attachments/assets/23f8a3a6-a8db-4d81-8c23-fd7a78864f2d"
/>

### Edit Pipeline
<img width="1512" height="791" alt="image"
src="https://github.com/user-attachments/assets/4c0a0c3c-81d8-4d15-91c7-6aa4f3de69c7"
/>

## After
### New Pipeline
<img width="757" height="395" alt="image"
src="https://github.com/user-attachments/assets/8a287cce-336c-4624-b4e9-a6a98eabb01d"
/>

### Edit Pipeline
<img width="1512" height="789" alt="image"
src="https://github.com/user-attachments/assets/9e096dbb-0a95-4a03-8d32-d5d71d2b57c5"
/>
2026-08-14 07:44:41 +01:00
Anthony Stirlinganddagecko 929ded41a8 Harden actions secret handling (#7435)
# Description of Changes

## Harden GitHub Actions secret handling

Moves secrets behind deployment environments, removes the GitHub App
token from
workflows that only comment and label, and moves PR preview images to
GHCR so the
preview path needs no registry credential.

Builds on #6005 by @dagecko — that commit is preserved with original
authorship,
rebased onto current main.

### Extract secrets from `run:` blocks (@dagecko, #6005 rebased)

- Secrets referenced in shell bodies moved to step-level `env:` so
values never
  reach a rendered command line
- Two `workflow_dispatch` inputs moved out of shell interpolation
  (`multiOSReleases`, `push-docker-base`)
- Dropped the hunks main has since solved — `setup-uv`, `reviewdog`,
`build-push-action` and `github-script` are all pinned newer on main now
- Fixed a bug in the original: `PR-Demo-cleanup.yml` uses a **quoted**
`<< 'ENDSSH'`
heredoc, so rewriting `${{ secrets.DOCKER_HUB_USERNAME }}` to
`${DOCKER_HUB_USERNAME}`
would have sent the literal string to the VPS and expanded to empty,
silently
  orphaning preview images behind `|| true`

### Gate secret-bearing jobs behind environments

- `environment:` added to 15 jobs across 10 workflows, mapping to
`release-signing`,
  `docker-publish`, `package-publish`, `pr-preview` and `bot-identity`
- Environment branch/tag policies are enforced by GitHub before the job
starts, so
  editing the workflow file cannot bypass them
- Four jobs deliberately **not** gated — `tauri-build`,
`frontend-backend-licenses-update`,
`swagger` and `push-docker-base` would fail their own triggers under the
current
  policies and need restructuring first
- Removed the `testMain` trigger from `push-docker` — the branch doesn't
exist and
  isn't in the environment's policy

### Publish PR previews to GHCR instead of Docker Hub

- Preview images now go to `ghcr.io/stirling-tools/stirling-pdf-test`,
authenticated
  with `GITHUB_TOKEN` rather than `DOCKER_HUB_API`
- Docker Hub personal access tokens cannot be scoped to a single
repository, so the
preview path was holding the same credential that publishes `s-pdf` and
`stirling-pdf`
- `DOCKER_HUB_API` no longer appears in any PR-reachable workflow
- Login now precedes every `docker manifest inspect` —
`deploy-on-v2-commit` had them
  reversed, which only worked because the Docker Hub repo was public

### Use `GITHUB_TOKEN` for comment and label workflows

- Seven workflows no longer mint a GitHub App token; only
`sync_files_v2`,
`sync-portal-docs` and `frontend-backend-licenses-update` still do, so
unattended
  auto-merge is unaffected
- `permissions:` blocks derived per job from the API calls each actually
makes —
these were previously inert, since an App installation token ignores
them, and one
  job had no block at all
- Comment-threading matchers updated to `github-actions[bot]` so
workflows still edit
  their own previous comment instead of posting duplicates
- Removed the App token from the `refs/pull/N/merge` checkout in
`PR-Demo-Comment-with-react` and set `persist-credentials: false` — it
was written
  into `.git/config` of an untrusted tree that the same job then builds
- Fixed a script injection in `check_toml.yml`: a fork-controlled branch
name was
interpolated into `actions/github-script` JS source, with validation
running after
the injected code had already executed. Values now come from
`process.env` and are
  validated before use.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.

---------

Co-authored-by: dagecko <cnyhuis@vigilantnow.com>
2026-08-13 23:03:47 +01:00
Ludy 5170509695 deps: upgrade mwiede JSch to 2.28.6 and adapt SFTP password handling (#7496)
# Description of Changes

This PR replaces #7490 and upgrades `com.github.mwiede:jsch` from
`0.2.23` to `2.28.6`.

In addition to the dependency bump from the original Dependabot PR, this
PR includes the required compatibility adjustment for SFTP password
authentication:

- Updated `jschVersion` in `build.gradle` from `0.2.23` to `2.28.6`.
- Updated `SftpFileClient` to pass the configured password to JSch as
UTF-8 encoded bytes instead of using the `String` overload.
- Preserved the existing SFTP connection and host-key verification
behavior.
- Addresses the API compatibility changes introduced by the newer JSch
version that prevented the dependency upgrade from being used unchanged.

This supersedes #7490

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-13 22:25:36 +01:00
briosandAnthony Stirling 4a2329ab6d refactor(hibernate): implement manual Hibernate-compliant equals/hashCode for entity classes (#6433)
# Description of Changes


This PR refactors our JPA entity classes to replace Lombok's `@Data` and
auto-generated `@EqualsAndHashCode` annotations with explicit Lombok
annotations and custom, JPA-compliant `equals()` and `hashCode()`
implementations.

### Rationale
Lombok's default `@Data` and `@EqualsAndHashCode` annotations are not
recommended for JPA entities. They often lead to:
- Severe performance issues (e.g., loading lazy collections when
evaluating `hashCode` or `toString`).
- Identity mismatches or collection bugs (e.g., when database-generated
IDs transition from `null` to assigned, breaking the entity's lookup in
a `Set` or `Map`).
This change ensures all JPA entities use safe Hibernate proxy checking
and use only the entity's database identifier for equality and hash code
calculations.


<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [X] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [X] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [X] I have performed a self-review of my own code
- [X] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [X] I have run `task check` to verify linters, typechecks, and tests
pass
- [X] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2026-08-13 22:24:45 +01:00
EthanHealy01 9ef20dcab8 Fix WebKit PDF-engine and storage failures, and catch them in cross-browser CI (#7366)
# Description of Changes

Follow-up to #7314, which fixed the IndexedDB blob rejection itself.
This one fixes the remaining WebKit engine gaps, fixes the ways that
class of failure surfaced to the user, and adds the cross-browser signal
that would have caught them on the PR instead of six weeks later.

## Why this exists

Two total WebKit outages sat on `main` for weeks:

1. pdf.js reads its text stream with `for await (… of readableStream)`,
and WebKit has no `ReadableStream[Symbol.asyncIterator]`. **All** pdf.js
text extraction threw `TypeError: undefined is not a function` —
Compare, read-aloud and the PDF text editor were dead on Safari.
2. IndexedDB in WebKit rejects Blob/File values with `UnknownError:
Error preparing Blob/File data to be stored in object store`, so nothing
persisted and every reload came back empty.

Neither was caught, because the existing specs never did the work. The
Compare specs filled both slots and asserted the button was enabled;
none of them clicked it. The persistence specs asserted a *filename*
reappeared after a reload, which only needs the metadata record, not the
bytes.

Every failure here **looked like success** — empty panes, blank
thumbnails, a `src` that was set but empty. That shapes the tests more
than the fixes.

## WebKit engine gaps

- **`ReadableStream[Symbol.asyncIterator]`**, installed at the entry
point before any PDF work starts. The lock discipline is the subtle
part: releasing is idempotent, is *not* done after a successful read,
and *is* done in the read's error steps — `for await` never calls
`return()` when `next()` rejects, so nothing else would ever unlock an
errored stream.
- **`requestIdleCallback`**, installed once instead of guarded at each
call site. This one wasn't broken, it was mistimed: the local fallbacks
fired at 200ms and 1000ms, landing the pdfium WASM compile on top of the
app's first renders. The shim honours the caller's full timeout, so
`{timeout: 2000}` means 2000ms.
- **`convertToBlob()` does not fail on a format it can't encode.** Per
spec it silently serialises to PNG, so asking for WebP and getting PNG
back looks like success. Canvas output now probes what the engine really
produced (once per realm) and uses the best lossy format it honours. PNG
of a rendered page is several times the size of the equivalent WebP or
JPEG, held as object URLs for every page on screen, on the engine with
the tightest renderer memory budget.

## WebKit storage failures

These read as generic transaction hygiene. They aren't — a refused blob
write **aborts its transaction**, which is the mechanism that turned a
WebKit rejection into a hang.

- **Blob refusal is remembered from any write**, not just the initial
`add`. WebKit reports it when it can't write the blob's *backing file*,
which is per-operation — an engine that accepted the add can still
refuse the rewrite, and every read-modify-write rewrites the record with
its body attached.
- **Aborted transactions no longer hang.** Read-modify-write moves to a
single `updateRecord` helper that owns its transaction, guards it once,
and resolves on **commit** rather than on the put's `onsuccess`. The
previous shape — two promises over one shared transaction, with an
`await` between the get and the put — put the abort guard on the read,
leaving the write with no handler at all. `persistVersionedOutputs`
awaits that, and `.catch` can't rescue a promise that never settles, so
tool outputs could silently stop persisting.
- **Stored blobs are no longer re-wrapped on read.** Since #7175 the
record holds the `File` itself; wrapping it in `new Blob([record.data])`
can cost WebKit the backing handle, giving you an object that looks
valid and reads as empty.
- **The file sidebar reaches a resting state** when the library can't be
read, instead of spinning forever on a rejection nobody observes. It
carries on with the in-memory workbench files: an unreadable library
should cost the user their history, not the file they're working on.
- **Thumbnail failures are logged.** Three `catch {}` blocks returned
`""`, and an empty thumbnail is indistinguishable from "this file has no
preview" — which is how outage #1 hid as a cosmetic nicety.

## CI

`main` now runs the whole stubbed suite once per engine (#7304), so the
new `@engine-capability` specs get chromium, firefox and webkit for
free. They assert the primitives actually work — a **counted**
comparison, a raster thumbnail data URL with real payload, and a page
rendered from a file restored by a reload — rather than that the UI
rendered. Deliberately small: anything added there is paid for three
times per PR, so add depth, not breadth. Run them alone with `task
e2e:cross-browser -- --grep @engine-capability`.

The cross-browser projects now share the stubbed project's viewport. At
the device presets' default 1280x720 a layout difference would fail
these specs on Firefox/WebKit only, which reads as an engine outage.

`vite.config.ts` gains a `worker.plugins` entry so `@app/*` resolves
inside worker bundles. Worker bundles are a separate Rollup pass and
don't inherit `plugins`, so the alias worked in the app and failed in a
worker — previously worked around with a relative import plus a lint
exemption, which silently bypasses the layer cascade.

## Verification

- `task frontend:check` green: typecheck, oxlint, theme lint, stylelint,
prettier, 215 test files / 1841 tests.
- The `@engine-capability` suite passes on Chromium and WebKit locally.
- **Negative control:** with the `ReadableStream` shim removed, the
WebKit comparison spec fails at the Deletions/Additions assertion — the
exact reported Safari symptom. Restored, and it passes. Both the fix and
the test that guards it are load-bearing.
- The worker alias change verified both ways: the build inlines the
encoding probe into the worker chunk, and removing `worker.plugins`
fails with `Rollup failed to resolve import
"@app/utils/canvasImageEncoding"`.
- The abort regression test aborts the transaction mid-write and asserts
`markFileAsProcessed` settles. Before the fix it never settles and the
test times out.

## Split out of this PR

Two things in earlier revisions of this branch were engine-agnostic —
found via the same symptom, not the same cause — and now have their own
PRs:

- **#7416** — blocked IndexedDB upgrades hanging the file library
(multi-tab lifecycle, the concurrent-open race, `onversionchange`).
- **#7417** — the thumbnail TTL rewriting the whole library on every
listing.

`FileSidebar`'s try/catch appears in both this PR and #7416,
identically: a WebKit rejection and a blocked-open rejection both have
to stop stranding the spinner. Whichever merges second is a no-op for
that file.

## Known gaps

- The blob-refused **rewrite** recovery in `updateRecord` isn't
unit-tested. `fake-indexeddb` never returns Blob values from a read, so
the branch that converts to a copy can't be reached there. Noted in the
test file.
- For the same reason, `fileFromRecord`'s "hand the stored File back
untouched" path is only covered on a real engine, by the reload spec.
- Nothing asserts that `src/index.tsx` imports the shims. The unit suite
installs the same module via `setupTests.ts` (jsdom has the same gaps
WebKit does), so a future regression where the entry point drops the
import would still be green under vitest.
- `FileSidebar`'s resting-state fix loses its E2E coverage until #7416
lands — forcing WebKit's blob refusal from a spec isn't practical, which
is why that spec blocks the database instead.

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [x] My changes generate no new warnings

### Documentation

- [x] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
2026-08-13 16:25:01 +00:00
imgbot[bot]andImgBotApp a7eb6ebcc3 [ImgBot] Optimize images (#7488)
## Beep boop. Your images are optimized!

Your image file size has been reduced by **16%** 🎉

<details>
<summary>
Details
</summary>

| File | Before | After | Percent reduction |
|:--|:--|:--|:--|
| /frontend/editor/src-tauri/icons/ios/AppIcon-512@2x.png | 32.25kb |
9.86kb | 69.44% |
| /frontend/editor/src/core/assets/brand/modern-logo/logo512.png |
7.96kb | 4.49kb | 43.61% |
| /app/core/src/main/resources/static/apple-touch-icon.png | 6.05kb |
3.55kb | 41.39% |
| /frontend/editor/src-tauri/icons/Square150x150Logo.png | 5.10kb |
3.13kb | 38.52% |
| /frontend/editor/public/og_images/saas/app-processor.png | 125.15kb |
77.41kb | 38.15% |
| /frontend/editor/public/mstile-150x150.png | 4.32kb | 2.68kb | 37.99%
|
| /frontend/editor/src-tauri/icons/mstile-150x150.png | 4.32kb | 2.68kb
| 37.99% |
| /frontend/editor/src-tauri/icons/Square142x142Logo.png | 4.89kb |
3.07kb | 37.28% |
| /frontend/editor/public/og_images/saas/app.png | 118.49kb | 76.07kb |
35.80% |
| /frontend/editor/src-tauri/icons/mstile-310x150.png | 4.77kb | 3.09kb
| 35.16% |
| /frontend/editor/public/mstile-310x150.png | 4.77kb | 3.09kb | 35.16%
|
|
/frontend/editor/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png
| 3.81kb | 2.50kb | 34.32% |
| /frontend/editor/public/og_images/saas/app-editor.png | 119.24kb |
78.94kb | 33.80% |
| /frontend/editor/src/core/assets/brand/modern-logo/logo192.png |
3.09kb | 2.06kb | 33.22% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-60x60@3x.png | 3.91kb |
2.73kb | 30.17% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-76x76@2x.png | 3.53kb |
2.48kb | 29.84% |
|
/frontend/editor/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png
| 3.36kb | 2.36kb | 29.82% |
| /frontend/editor/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png
| 3.36kb | 2.36kb | 29.82% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png | 3.77kb
| 2.65kb | 29.66% |
| /frontend/editor/src-tauri/icons/64x64.png | 2.46kb | 1.86kb | 24.53%
|
| /frontend/editor/src-tauri/icons/ios/AppIcon-29x29@3x.png | 2.37kb |
1.82kb | 23.05% |
| /frontend/editor/src-tauri/icons/Square89x89Logo.png | 3.24kb | 2.50kb
| 22.64% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-40x40@2x-1.png | 2.27kb |
1.76kb | 22.22% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-40x40@2x.png | 2.27kb |
1.76kb | 22.22% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-76x76@1x.png | 2.19kb |
1.70kb | 22.17% |
| /frontend/editor/src/core/assets/brand/classic-logo/logo512.png |
99.72kb | 78.32kb | 21.47% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-60x60@2x.png | 2.35kb |
1.86kb | 20.92% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-40x40@3x.png | 2.35kb |
1.86kb | 20.92% |
| /frontend/editor/src/core/assets/brand/modern-logo/Firstpage.png |
210.88kb | 169.27kb | 19.73% |
| /frontend/editor/src-tauri/icons/Square71x71Logo.png | 2.73kb | 2.25kb
| 17.59% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-29x29@2x.png | 1.71kb |
1.43kb | 16.47% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-29x29@2x-1.png | 1.71kb |
1.43kb | 16.47% |
| /frontend/editor/src-tauri/icons/Square44x44Logo.png | 1.72kb | 1.46kb
| 15.31% |
| /frontend/editor/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png |
1.90kb | 1.62kb | 14.63% |
|
/frontend/editor/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png
| 1.90kb | 1.62kb | 14.63% |
|
/frontend/editor/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png
| 1.79kb | 1.54kb | 14.25% |
| /frontend/editor/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png |
1.79kb | 1.54kb | 14.25% |
| /frontend/editor/src-tauri/icons/Square284x284Logo.png | 9.32kb |
8.03kb | 13.78% |
| /app/core/src/main/resources/static/images/signature.png | 20.06kb |
17.39kb | 13.30% |
| /docs/stirling.png | 20.06kb | 17.39kb | 13.30% |
| /frontend/editor/src-tauri/icons/android-chrome-512x512.png | 20.06kb
| 17.39kb | 13.30% |
| /frontend/editor/public/android-chrome-512x512.png | 20.06kb | 17.39kb
| 13.30% |
| /frontend/editor/public/favicon.png | 20.06kb | 17.39kb | 13.30% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-20x20@3x.png | 1.67kb |
1.45kb | 13.03% |
| /frontend/editor/src-tauri/icons/Square310x310Logo.png | 10.43kb |
9.09kb | 12.79% |
| /frontend/editor/src/core/assets/brand/classic-logo/Firstpage.png |
405.84kb | 355.87kb | 12.31% |
|
/frontend/editor/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png
| 16.87kb | 14.83kb | 12.09% |
|
/frontend/editor/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png
| 10.69kb | 9.41kb | 11.98% |
| /frontend/editor/src-tauri/icons/128x128@2x.png | 8.59kb | 7.60kb |
11.55% |
|
/frontend/editor/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png
| 6.70kb | 5.99kb | 10.70% |
| /frontend/editor/src-tauri/icons/192x192.png | 6.10kb | 5.47kb |
10.36% |
| /frontend/editor/public/android-chrome-192x192.png | 6.10kb | 5.47kb |
10.36% |
| /frontend/editor/src-tauri/icons/android-chrome-192x192.png | 6.10kb |
5.47kb | 10.36% |
|
/frontend/editor/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png
| 6.29kb | 5.67kb | 9.85% |
|
/frontend/editor/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png
| 6.29kb | 5.67kb | 9.85% |
| /frontend/editor/src/core/assets/brand/classic-logo/logo192.png |
23.45kb | 21.19kb | 9.62% |
|
/frontend/editor/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png
| 5.21kb | 4.74kb | 9.06% |
| /frontend/editor/src-tauri/icons/mstile-310x310.png | 11.48kb |
10.45kb | 8.95% |
| /frontend/editor/public/mstile-310x310.png | 11.48kb | 10.45kb | 8.95%
|
| /frontend/editor/public/mstile-144x144.png | 4.98kb | 4.53kb | 8.95% |
| /frontend/editor/src-tauri/icons/mstile-144x144.png | 4.98kb | 4.53kb
| 8.95% |
|
/frontend/editor/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png
| 4.87kb | 4.44kb | 8.89% |
| /frontend/editor/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png
| 4.87kb | 4.44kb | 8.89% |
| /frontend/editor/src-tauri/icons/StoreLogo.png | 1.89kb | 1.76kb |
7.03% |
| /frontend/editor/src-tauri/icons/128x128.png | 4.16kb | 3.95kb | 5.07%
|
| /app/core/src/main/resources/static/favicon-32x32.png | 1.29kb |
1.23kb | 4.90% |
| /frontend/editor/src-tauri/icons/32x32.png | 1.29kb | 1.23kb | 4.90% |
| /frontend/editor/src/core/assets/login/microsoft.svg | 0.29kb | 0.27kb
| 4.79% |
| /frontend/editor/src-tauri/icons/Square107x107Logo.png | 3.72kb |
3.54kb | 4.78% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-40x40@1x.png | 1.13kb |
1.08kb | 4.25% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-20x20@2x-1.png | 1.13kb |
1.08kb | 4.25% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-20x20@2x.png | 1.13kb |
1.08kb | 4.25% |
| /frontend/editor/public/og_images/shared-sign.png | 620.42kb |
597.33kb | 3.72% |
| /frontend/editor/src-tauri/icons/mstile-70x70.png | 3.34kb | 3.25kb |
2.66% |
| /frontend/editor/public/mstile-70x70.png | 3.34kb | 3.25kb | 2.66% |
| /frontend/editor/src/core/assets/login/authentik.svg | 6.85kb | 6.81kb
| 0.70% |
| /frontend/editor/src/core/assets/brand/classic-logo/logo-tooltip.svg |
1.49kb | 1.48kb | 0.59% |
| /frontend/editor/src/core/assets/login/oidc.svg | 11.32kb | 11.27kb |
0.43% |
| /frontend/editor/src/core/assets/login/github.svg | 1.44kb | 1.44kb |
0.20% |
| | | | |
| **Total :** | **2,144.28kb** | **1,792.57kb** | **16.40%** |
</details>

---

[📝 docs](https://imgbot.net/docs) | [:octocat:
repo](https://github.com/imgbot/ImgBot) | [🙋🏾
issues](https://github.com/imgbot/ImgBot/issues) | [🏪
marketplace](https://github.com/marketplace/imgbot)

<i>~Imgbot - Part of [Optimole](https://optimole.com/) family</i>

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com>
2026-08-13 14:09:25 +00:00
Ludy 1659ca964a ci: include buildSrc in dependency updates and Gradle cache keys (#7486)
## Description of Changes

- Add `/app/saas` and `/buildSrc` to Dependabot's Gradle update
directories.
- Include `buildSrc/**` in every Gradle `actions/cache` key.
- Ensure changes to shared Gradle build logic invalidate the dependency
cache.

This keeps dependency updates and CI caching aligned with the
repository's current Gradle build structure.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)

### Testing

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally.
2026-08-13 13:51:00 +00:00
Reece BrowneandEthanHealy01 d2844c4f90 Super search across Editor and Processor (#6734)
One search bar for the whole app, in both the Editor and the Processor.
**Cmd/Ctrl+K** from anywhere.

- **Editor** — your files, tools, settings, and everything in the
Processor: its pages plus live users, policies, pipelines, sources, and
full-text developer docs.
- **Processor** — the same bar over its own data (users, policies,
pipelines, sources, docs) plus tools and settings. Files stay
editor-only; pages stay out (the sidebar covers them).
- **Filter chips** (and typed prefixes like `policy: invoice`) narrow to
one lane; each group shows a few results with **Show more** to expand.
- **Access-gated**: users only see lanes and settings sections they can
actually open — no Processor chips, results, or entity fetches without
portal access, no admin settings for non-admins, no account-bound
sections for anonymous SaaS sessions. Asserted by unit tests and a
stubbed Playwright suite.
- Selecting a result takes you straight there, across apps when needed —
results group under collapsible sections.

Replaces the separate search boxes that previously lived in the file
sidebar, the tool panel header, the settings modal, and the docs page.

### Editor

<img
src="https://gist.githubusercontent.com/reecebrowne/5879e797c5ab6abc027a7bfd5cd4de17/raw/editor-search.png?v=2"
width="800" alt="Editor super search" />

### Processor

<img
src="https://gist.githubusercontent.com/reecebrowne/5879e797c5ab6abc027a7bfd5cd4de17/raw/portal-search.png?v=2"
width="800" alt="Processor super search" />

---------

Co-authored-by: EthanHealy01 <ethan.healy.21@gmail.com>
2026-08-13 12:38:45 +00:00
brios cd49daf5c6 refactor(api): move DeletingRandomAccessFile to CustomPDFDocumentFactory as a private static class (#7344)
# Description of Changes

It was in a seperate dir
(app/common/src/main/java/org/apache/pdfbox/examples/util/) which i felt
out of place for it.



<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [X] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [X] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [X] I have performed a self-review of my own code
- [X] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [X] I have run `task check` to verify linters, typechecks, and tests
pass
- [X] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-13 11:16:09 +00:00
Anthony Stirling 18e056ea8d feat: switch default branding to the modern Stirling logo (#7485)
# Description of Changes

logo changes!

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-13 11:03:28 +00:00
brios 5709edccc0 fix(ui): update MergeFileSorter button size from 'md' to 'sm' (#7328)
# Description of Changes


Fixes height of direction arrow button height to match the height of the
dropdown select element

### New

<img width="598" height="1158" alt="image"
src="https://github.com/user-attachments/assets/9461d4e1-060c-43b6-ac15-eb906f80134a"
/>


### Old
<img width="596" height="1162" alt="image"
src="https://github.com/user-attachments/assets/96ce8317-527d-4e1b-815d-15349adc9aea"
/>


<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [X] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [X] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [X] I have performed a self-review of my own code
- [X] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [X] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [X] I have run `task check` to verify linters, typechecks, and tests
pass
- [X] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-13 10:34:57 +00:00
James Brunton 09e548fbac Fix a11y on DividerWithText (#7482)
# Description of Changes
Nightlies are currently failing with an a11y issue on the
DividerWithText story. This fixes the a11y issue.

<img width="1440" height="1240" alt="image"
src="https://github.com/user-attachments/assets/c1623583-a68c-46d9-8cdb-453aca5ac20d"
/>
2026-08-13 11:10:31 +01:00
brios 1447ed8b62 refactor(redact): replace PDFBox-based text redaction with JPDFium (#7364)
# Description of Changes

Refactors automatic text redaction to use JPDFium-based redaction/text
removal instead of PDFBox

Changes:

* The `RedactController` now uses the JPDFium native redaction engine
(`PdfRedactor.redact`) as the primary method for PDF redaction, with
automatic fallback to the manual redaction service if JPDFium fails or
throws an exception. This improves reliability and leverages more robust
native features when available.
* Regex patterns provided by the user are now validated before redaction
begins, ensuring invalid patterns are rejected early with clear error
messages.
* The code now trims and filters out empty or excessively long redaction
terms, preventing unnecessary processing and potential errors.


<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [x] I have run `task check` to verify linters, typechecks, and tests
pass
- [x] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-13 11:10:10 +01:00
dependabot[bot] b33c2d68d6 build(deps): bump springSecuritySamlVersion from 7.0.5 to 7.1.0 (#7399)
Bumps `springSecuritySamlVersion` from 7.0.5 to 7.1.0.
Updates `org.springframework.security:spring-security-core` from 7.0.5
to 7.1.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/spring-projects/spring-security/releases">org.springframework.security:spring-security-core's
releases</a>.</em></p>
<blockquote>
<h2>7.1.0</h2>
<h2>🪲 Bug Fixes</h2>
<ul>
<li>Opaque token introspectors should not allow empty credentials <a
href="https://redirect.github.com/spring-projects/spring-security/issues/19201">#19201</a></li>
</ul>
<h2>🔨 Dependency Upgrades</h2>
<ul>
<li>Bump <code>@springio</code>/antora-extensions from 1.14.11 to
1.14.12 in /docs <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19235">#19235</a></li>
<li>Bump actions/checkout from 6.0.2 to 6.0.3 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19271">#19271</a></li>
<li>Bump antora from 3.2.0-alpha.11 to 3.2.0-alpha.12 in /docs <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19181">#19181</a></li>
<li>Bump ch.qos.logback:logback-classic from 1.5.32 to 1.5.33 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19228">#19228</a></li>
<li>Bump ch.qos.logback:logback-classic from 1.5.33 to 1.5.34 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19268">#19268</a></li>
<li>Bump com.fasterxml.jackson:jackson-bom from 2.21.2 to 2.21.3 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19133">#19133</a></li>
<li>Bump com.fasterxml.jackson:jackson-bom from 2.21.3 to 2.22.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19246">#19246</a></li>
<li>Bump com.google.code.gson:gson from 2.13.2 to 2.14.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19125">#19125</a></li>
<li>Bump com.nimbusds:oauth2-oidc-sdk from 11.37 to 11.37.1 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19157">#19157</a></li>
<li>Bump com.nimbusds:oauth2-oidc-sdk from 11.37 to 11.37.2 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19195">#19195</a></li>
<li>Bump com.webauthn4j:webauthn4j-core from 0.31.3.RELEASE to
0.31.5.RELEASE <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19148">#19148</a></li>
<li>Bump com.webauthn4j:webauthn4j-core from 0.31.5.RELEASE to
0.31.6.RELEASE <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19263">#19263</a></li>
<li>Bump gradle-wrapper from 9.4.1 to 9.5.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19135">#19135</a></li>
<li>Bump gradle-wrapper from 9.5.0 to 9.5.1 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19171">#19171</a></li>
<li>Bump io-micrometer from 1.16.5 to 1.17.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19287">#19287</a></li>
<li>Bump io.mockk:mockk from 1.14.9 to 1.14.11 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19244">#19244</a></li>
<li>Bump io.projectreactor:reactor-bom from 2025.0.5 to 2025.0.6 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19296">#19296</a></li>
<li>Bump org-jetbrains-kotlin from 2.3.20 to 2.3.21 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19126">#19126</a></li>
<li>Bump org-jetbrains-kotlin from 2.3.21 to 2.4.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19264">#19264</a></li>
<li>Bump org-opensaml5 from 5.2.1 to 5.2.2 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19176">#19176</a></li>
<li>Bump org.apache.maven:maven-resolver-provider from 3.9.15 to 3.9.16
<a
href="https://redirect.github.com/spring-projects/spring-security/pull/19190">#19190</a></li>
<li>Bump org.apereo.cas.client:cas-client-core from 4.1.0 to 4.1.1 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19200">#19200</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.1.Final to
7.3.2.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19119">#19119</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.2.Final to
7.3.3.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19149">#19149</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.3.Final to
7.3.4.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19165">#19165</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.4.Final to
7.3.5.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19191">#19191</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.5.Final to
7.3.6.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19211">#19211</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.6.Final to
7.4.0.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19226">#19226</a></li>
<li>Bump org.jetbrains.kotlinx:kotlinx-coroutines-bom from 1.10.2 to
1.11.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19166">#19166</a></li>
<li>Bump org.junit:junit-bom from 6.0.3 to 6.1.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19197">#19197</a></li>
<li>Bump org.slf4j:slf4j-api from 2.0.17 to 2.0.18 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19169">#19169</a></li>
<li>Bump org.springframework.data:spring-data-bom from 2025.1.5 to
2025.1.6 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19290">#19290</a></li>
<li>Bump org.springframework.ldap:spring-ldap-core from 4.0.3 to 4.1.0
<a
href="https://redirect.github.com/spring-projects/spring-security/pull/19291">#19291</a></li>
<li>Bump org.springframework:spring-framework-bom from 7.0.7 to 7.0.8 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19285">#19285</a></li>
<li>Bump spring-io/spring-release-actions from 0.0.4 to 0.0.5 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19179">#19179</a></li>
<li>Bump tools.jackson:jackson-bom from 3.1.2 to 3.1.3 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19147">#19147</a></li>
<li>Bump tools.jackson:jackson-bom from 3.1.3 to 3.1.4 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19245">#19245</a></li>
<li>Bump tools.jackson:jackson-bom from 3.1.4 to 3.2.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19286">#19286</a></li>
<li>Update to spring-data-bom 2026.0.0 <a
href="https://redirect.github.com/spring-projects/spring-security/issues/19303">#19303</a></li>
</ul>
<h2>🔩 Build Updates</h2>
<ul>
<li>Release 7.1.0 <a
href="https://redirect.github.com/spring-projects/spring-security/issues/19218">#19218</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/spring-projects/spring-security/commit/8078781a6bdbd0c44755d24e13839d041caece34"><code>8078781</code></a>
Release 7.1.0</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/835ca03dd906e80ab1a97759f8ecc8dd30a93e04"><code>835ca03</code></a>
Merge branch '7.0.x'</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/15e2488c84dbf0eac0f5d3f39cf8f1c49a07abd9"><code>15e2488</code></a>
Sync branch 'main'</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/62c4d60524ba0b25d6f0e969ce08dca3987a5019"><code>62c4d60</code></a>
Merge branch '6.5.x' into 7.0.x</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/d884913175bcedb63ef1f7e44799de1ee2cd18f2"><code>d884913</code></a>
Sync branch '7.0.x'</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/fd5dae5e4d3fece143d03f9ca2eba10ff3a420fc"><code>fd5dae5</code></a>
Sync branch '6.5.x'</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/65f43d6894eb304ad2ecc33739aeae7f02d49efd"><code>65f43d6</code></a>
Merge branch '7.0.x'</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/d29c76113545c22da5583c802ce419b116f5d678"><code>d29c761</code></a>
Merge branch '6.5.x' into 7.0.x</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/700a4533aa70603d4076dc5ebf052b72312a39e5"><code>700a453</code></a>
Bump ch.qos.logback:logback-classic from 1.5.32 to 1.5.34</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/831bfd7ffa3dc5d2790ed5ffedcfb9f6b78924da"><code>831bfd7</code></a>
Update to spring-data-bom 2026.0.0</li>
<li>Additional commits viewable in <a
href="https://github.com/spring-projects/spring-security/compare/7.0.5...7.1.0">compare
view</a></li>
</ul>
</details>
<br />

Updates
`org.springframework.security:spring-security-saml2-service-provider`
from 7.0.5 to 7.1.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/spring-projects/spring-security/releases">org.springframework.security:spring-security-saml2-service-provider's
releases</a>.</em></p>
<blockquote>
<h2>7.1.0</h2>
<h2>🪲 Bug Fixes</h2>
<ul>
<li>Opaque token introspectors should not allow empty credentials <a
href="https://redirect.github.com/spring-projects/spring-security/issues/19201">#19201</a></li>
</ul>
<h2>🔨 Dependency Upgrades</h2>
<ul>
<li>Bump <code>@springio</code>/antora-extensions from 1.14.11 to
1.14.12 in /docs <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19235">#19235</a></li>
<li>Bump actions/checkout from 6.0.2 to 6.0.3 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19271">#19271</a></li>
<li>Bump antora from 3.2.0-alpha.11 to 3.2.0-alpha.12 in /docs <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19181">#19181</a></li>
<li>Bump ch.qos.logback:logback-classic from 1.5.32 to 1.5.33 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19228">#19228</a></li>
<li>Bump ch.qos.logback:logback-classic from 1.5.33 to 1.5.34 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19268">#19268</a></li>
<li>Bump com.fasterxml.jackson:jackson-bom from 2.21.2 to 2.21.3 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19133">#19133</a></li>
<li>Bump com.fasterxml.jackson:jackson-bom from 2.21.3 to 2.22.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19246">#19246</a></li>
<li>Bump com.google.code.gson:gson from 2.13.2 to 2.14.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19125">#19125</a></li>
<li>Bump com.nimbusds:oauth2-oidc-sdk from 11.37 to 11.37.1 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19157">#19157</a></li>
<li>Bump com.nimbusds:oauth2-oidc-sdk from 11.37 to 11.37.2 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19195">#19195</a></li>
<li>Bump com.webauthn4j:webauthn4j-core from 0.31.3.RELEASE to
0.31.5.RELEASE <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19148">#19148</a></li>
<li>Bump com.webauthn4j:webauthn4j-core from 0.31.5.RELEASE to
0.31.6.RELEASE <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19263">#19263</a></li>
<li>Bump gradle-wrapper from 9.4.1 to 9.5.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19135">#19135</a></li>
<li>Bump gradle-wrapper from 9.5.0 to 9.5.1 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19171">#19171</a></li>
<li>Bump io-micrometer from 1.16.5 to 1.17.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19287">#19287</a></li>
<li>Bump io.mockk:mockk from 1.14.9 to 1.14.11 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19244">#19244</a></li>
<li>Bump io.projectreactor:reactor-bom from 2025.0.5 to 2025.0.6 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19296">#19296</a></li>
<li>Bump org-jetbrains-kotlin from 2.3.20 to 2.3.21 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19126">#19126</a></li>
<li>Bump org-jetbrains-kotlin from 2.3.21 to 2.4.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19264">#19264</a></li>
<li>Bump org-opensaml5 from 5.2.1 to 5.2.2 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19176">#19176</a></li>
<li>Bump org.apache.maven:maven-resolver-provider from 3.9.15 to 3.9.16
<a
href="https://redirect.github.com/spring-projects/spring-security/pull/19190">#19190</a></li>
<li>Bump org.apereo.cas.client:cas-client-core from 4.1.0 to 4.1.1 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19200">#19200</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.1.Final to
7.3.2.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19119">#19119</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.2.Final to
7.3.3.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19149">#19149</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.3.Final to
7.3.4.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19165">#19165</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.4.Final to
7.3.5.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19191">#19191</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.5.Final to
7.3.6.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19211">#19211</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.6.Final to
7.4.0.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19226">#19226</a></li>
<li>Bump org.jetbrains.kotlinx:kotlinx-coroutines-bom from 1.10.2 to
1.11.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19166">#19166</a></li>
<li>Bump org.junit:junit-bom from 6.0.3 to 6.1.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19197">#19197</a></li>
<li>Bump org.slf4j:slf4j-api from 2.0.17 to 2.0.18 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19169">#19169</a></li>
<li>Bump org.springframework.data:spring-data-bom from 2025.1.5 to
2025.1.6 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19290">#19290</a></li>
<li>Bump org.springframework.ldap:spring-ldap-core from 4.0.3 to 4.1.0
<a
href="https://redirect.github.com/spring-projects/spring-security/pull/19291">#19291</a></li>
<li>Bump org.springframework:spring-framework-bom from 7.0.7 to 7.0.8 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19285">#19285</a></li>
<li>Bump spring-io/spring-release-actions from 0.0.4 to 0.0.5 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19179">#19179</a></li>
<li>Bump tools.jackson:jackson-bom from 3.1.2 to 3.1.3 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19147">#19147</a></li>
<li>Bump tools.jackson:jackson-bom from 3.1.3 to 3.1.4 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19245">#19245</a></li>
<li>Bump tools.jackson:jackson-bom from 3.1.4 to 3.2.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19286">#19286</a></li>
<li>Update to spring-data-bom 2026.0.0 <a
href="https://redirect.github.com/spring-projects/spring-security/issues/19303">#19303</a></li>
</ul>
<h2>🔩 Build Updates</h2>
<ul>
<li>Release 7.1.0 <a
href="https://redirect.github.com/spring-projects/spring-security/issues/19218">#19218</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/spring-projects/spring-security/commit/8078781a6bdbd0c44755d24e13839d041caece34"><code>8078781</code></a>
Release 7.1.0</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/835ca03dd906e80ab1a97759f8ecc8dd30a93e04"><code>835ca03</code></a>
Merge branch '7.0.x'</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/15e2488c84dbf0eac0f5d3f39cf8f1c49a07abd9"><code>15e2488</code></a>
Sync branch 'main'</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/62c4d60524ba0b25d6f0e969ce08dca3987a5019"><code>62c4d60</code></a>
Merge branch '6.5.x' into 7.0.x</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/d884913175bcedb63ef1f7e44799de1ee2cd18f2"><code>d884913</code></a>
Sync branch '7.0.x'</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/fd5dae5e4d3fece143d03f9ca2eba10ff3a420fc"><code>fd5dae5</code></a>
Sync branch '6.5.x'</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/65f43d6894eb304ad2ecc33739aeae7f02d49efd"><code>65f43d6</code></a>
Merge branch '7.0.x'</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/d29c76113545c22da5583c802ce419b116f5d678"><code>d29c761</code></a>
Merge branch '6.5.x' into 7.0.x</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/700a4533aa70603d4076dc5ebf052b72312a39e5"><code>700a453</code></a>
Bump ch.qos.logback:logback-classic from 1.5.32 to 1.5.34</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/831bfd7ffa3dc5d2790ed5ffedcfb9f6b78924da"><code>831bfd7</code></a>
Update to spring-data-bom 2026.0.0</li>
<li>Additional commits viewable in <a
href="https://github.com/spring-projects/spring-security/compare/7.0.5...7.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 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 18:11:34 +00:00
dependabot[bot] 519b00ba35 build(deps): bump the gradle group across 2 directories with 1 update (#7458)
Bumps the gradle group with 1 update in the /docker/backend directory:
gradle.
Bumps the gradle group with 1 update in the /docker/embedded directory:
gradle.

Updates `gradle` from `934a520` to `e8aeffb`

Updates `gradle` from `934a520` to `e8aeffb`


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 16:37:38 +00:00
dependabot[bot] d19c2a1143 build(deps): bump the ubuntu group across 2 directories with 1 update (#7452)
Bumps the ubuntu group with 1 update in the /docker/base directory:
ubuntu.
Bumps the ubuntu group with 1 update in the /docker/unoserver directory:
ubuntu.

Updates `ubuntu` from `4fbb8e6` to `561618e`

Updates `ubuntu` from `4fbb8e6` to `561618e`

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-12 16:24:48 +00:00
dependabot[bot] 833d951fd0 build(deps): bump actions/stale from 10.3.0 to 11.0.0 (#7376)
Bumps [actions/stale](https://github.com/actions/stale) from 10.3.0 to
11.0.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/stale/releases">actions/stale's
releases</a>.</em></p>
<blockquote>
<h2>v11.0.0</h2>
<h2>What's Changed</h2>
<h3>Enhancement</h3>
<ul>
<li>Migrate to ESM and update dependencies by <a
href="https://github-grid.enterprise.slack.com/team/U08CVLQ4JKE"><code>@​chiranjib-swain</code></a>
in <a
href="https://redirect.github.com/actions/stale/pull/1350">actions/stale#1350</a></li>
</ul>
<h3>Dependency Update</h3>
<ul>
<li>Override brace-expansion to 5.0.8 to address 24 high-severity
dependency vulnerabilities by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/stale/pull/1351">actions/stale#1351</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/stale/compare/v10...v11.0.0">https://github.com/actions/stale/compare/v10...v11.0.0</a></p>
<h2>v10.4.0</h2>
<h2>What's Changed</h2>
<h3>Bug Fix</h3>
<ul>
<li>Fixed <code>only-issue-types</code> validation by <a
href="https://github.com/trueberryless"><code>@​trueberryless</code></a>
in <a
href="https://redirect.github.com/actions/stale/pull/1338">actions/stale#1338</a></li>
</ul>
<h3>Dependency Updates</h3>
<ul>
<li>Bump undici to 6.27.0 via override, clean up stale license files,
and version to 10.4.0. by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/stale/pull/1342">actions/stale#1342</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/trueberryless"><code>@​trueberryless</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/stale/pull/1338">actions/stale#1338</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/stale/compare/v10.3.0...v10.4.0">https://github.com/actions/stale/compare/v10.3.0...v10.4.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/stale/commit/4391f3da665fdf50b6810c1a66712fb9ba21aa93"><code>4391f3d</code></a>
Fix 24 high severity vulnerabilities by overriding brace-expansion to
5.0.8 (...</li>
<li><a
href="https://github.com/actions/stale/commit/eaf9131fae5eafd0c31a64ebe3a2e183266fec48"><code>eaf9131</code></a>
refactor: update imports to use ES module syntax and improve test
structure (...</li>
<li><a
href="https://github.com/actions/stale/commit/1e223db275d687790206a7acac4d1a11bd6fe629"><code>1e223db</code></a>
Bump undici to 6.27.0 via override, clean up stale license files, and
version...</li>
<li><a
href="https://github.com/actions/stale/commit/9461cb10066d1553762bac6a02599ab8c26b14dd"><code>9461cb1</code></a>
fix: <code>only-issue-types</code> does not affect PRs (<a
href="https://redirect.github.com/actions/stale/issues/1338">#1338</a>)</li>
<li>See full diff in <a
href="https://github.com/actions/stale/compare/eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899...4391f3da665fdf50b6810c1a66712fb9ba21aa93">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/stale&package-manager=github_actions&previous-version=10.3.0&new-version=11.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-08-12 16:13:59 +00:00
dependabot[bot]andLudy 551af7fda3 build(deps): bump github/codeql-action/upload-sarif from 4.37.3 to 4.37.4 (#7373)
Bumps
[github/codeql-action/upload-sarif](https://github.com/github/codeql-action)
from 4.37.3 to 4.37.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/upload-sarif's
releases</a>.</em></p>
<blockquote>
<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/upload-sarif'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/f205ea1c3313d32999d8d6a48b4f6530d4437b38"><code>f205ea1</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4053">#4053</a>
from github/update-v4.37.4-9130ce0f7</li>
<li><a
href="https://github.com/github/codeql-action/commit/e40d079dd9dd4a5c74f625cecd83867c8208aa71"><code>e40d079</code></a>
Update changelog for v4.37.4</li>
<li><a
href="https://github.com/github/codeql-action/commit/9130ce0f733a0d17b9e1b9692c2af19be6c69dda"><code>9130ce0</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4051">#4051</a>
from github/update-bundle/codeql-bundle-v2.26.2</li>
<li><a
href="https://github.com/github/codeql-action/commit/c62d82468641dca0f8df108ab73e2a8407ac9cf7"><code>c62d824</code></a>
Add changelog note</li>
<li><a
href="https://github.com/github/codeql-action/commit/da0c1901011e62af9c02aae8bf5b8885b11f7741"><code>da0c190</code></a>
Update default bundle to codeql-bundle-v2.26.2</li>
<li><a
href="https://github.com/github/codeql-action/commit/18420e3271f74589575af831a523c833acda327f"><code>18420e3</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4043">#4043</a>
from github/mbg/ts/changelog</li>
<li><a
href="https://github.com/github/codeql-action/commit/7e8d8970f03ec5a78ab372fc0778e8e4194111a5"><code>7e8d897</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4046">#4046</a>
from github/mbg/repo-prop/code-quality</li>
<li><a
href="https://github.com/github/codeql-action/commit/2d4c474c2ca5ea2965b9e53fabb7b67b0100016c"><code>2d4c474</code></a>
Log <code>!analysisKindSupported</code> case</li>
<li><a
href="https://github.com/github/codeql-action/commit/98c05a17d327d7c4055fca83114434ab56baacf6"><code>98c05a1</code></a>
Fix argument validation in <code>rollback-changelog.ts</code></li>
<li><a
href="https://github.com/github/codeql-action/commit/8289a49271cbb335d374e7e2e7a50c1576be0afe"><code>8289a49</code></a>
Ignore repository property for unsupported analysis kinds</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81...f205ea1c3313d32999d8d6a48b4f6530d4437b38">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: Ludy <Ludy87@users.noreply.github.com>
2026-08-12 15:56:30 +00:00
NathanandAnthony Stirling 9cfd24cf08 docs: request sample files in issue templates (#6770)
## Summary
- add sample/example file prompts to the bug report and feature request
issue forms
- remind reporters to remove sensitive information before attaching
files

Closes #4480

## Checks
- `git diff --check`
- parsed both updated issue template YAML files with PyYAML and verified
the `sample-files` field is present

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2026-08-12 15:54:49 +00:00
dependabot[bot] bb9087f3f9 build(deps): bump KSXGitHub/github-actions-deploy-aur from 4.1.3 to 4.2.0 (#7374)
Bumps
[KSXGitHub/github-actions-deploy-aur](https://github.com/ksxgithub/github-actions-deploy-aur)
from 4.1.3 to 4.2.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/ksxgithub/github-actions-deploy-aur/releases">KSXGitHub/github-actions-deploy-aur's
releases</a>.</em></p>
<blockquote>
<h2>v4.2.0</h2>
<p>Add a feature to sync AUR repo (<a
href="https://redirect.github.com/KSXGitHub/github-actions-deploy-aur/pull/52">KSXGitHub/github-actions-deploy-aur#52</a>).</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/KSXGitHub/github-actions-deploy-aur/commit/084b0d9b15415bf9cdb65d44dad1efe37a354050"><code>084b0d9</code></a>
style: consistency (<a
href="https://redirect.github.com/ksxgithub/github-actions-deploy-aur/issues/55">#55</a>)</li>
<li><a
href="https://github.com/KSXGitHub/github-actions-deploy-aur/commit/9e2f21095c586521806151200746082d8e02bb90"><code>9e2f210</code></a>
fix: force-add <code>PKGBUILD</code> and <code>.SRCINFO</code> in the
asset_dir/assets path (<a
href="https://redirect.github.com/ksxgithub/github-actions-deploy-aur/issues/53">#53</a>)</li>
<li><a
href="https://github.com/KSXGitHub/github-actions-deploy-aur/commit/7acb32fe8c43848121eee16dd39d8294ec2bf25b"><code>7acb32f</code></a>
feat: full asset sync (<a
href="https://redirect.github.com/ksxgithub/github-actions-deploy-aur/issues/52">#52</a>)</li>
<li>See full diff in <a
href="https://github.com/ksxgithub/github-actions-deploy-aur/compare/da03e160361ce01bf087e790b6ffd196d7dccff7...084b0d9b15415bf9cdb65d44dad1efe37a354050">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=KSXGitHub/github-actions-deploy-aur&package-manager=github_actions&previous-version=4.1.3&new-version=4.2.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-08-12 15:53:38 +00:00
dependabot[bot] 2f61a8f4ff build(deps): bump actions/upload-artifact from 7.0.0 to 7.0.1 (#7375)
Bumps
[actions/upload-artifact](https://github.com/actions/upload-artifact)
from 7.0.0 to 7.0.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/upload-artifact/releases">actions/upload-artifact's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Update the readme with direct upload details by <a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/upload-artifact/pull/795">actions/upload-artifact#795</a></li>
<li>Readme: bump all the example versions to v7 by <a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/upload-artifact/pull/796">actions/upload-artifact#796</a></li>
<li>Include changes in typespec/ts-http-runtime 0.3.5 by <a
href="https://github.com/yacaovsnc"><code>@​yacaovsnc</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/797">actions/upload-artifact#797</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/upload-artifact/compare/v7...v7.0.1">https://github.com/actions/upload-artifact/compare/v7...v7.0.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/actions/upload-artifact/compare/v7...043fb46d1a93c77aae656e7c1c64a875d1fc6a0a">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-12 15:45:37 +00:00
Samuel SaezandClaude Opus 5 ba06a1dc1b fix(desktop): stop Task from eating the PowerShell variables in jlink… (#7258)
…:runtime

The Windows branch of jlink:runtime clears the read-only attribute jlink
leaves on the bundled JRE, so Tauri can overwrite the staged copies. It
never worked.

Task does not hand the command to cmd.exe; it runs it through its own
POSIX shell, which expands `$_` and `$false` as shell variables. Neither
is set, so both became empty and PowerShell was asked to run

    ForEach-Object { .IsReadOnly = }

which errors on every file. Verified against a directory of read-only
files: the double-quoted form leaves 3 of 3 still read-only and exits
non-zero, the single-quoted form clears all 3 and exits 0.

Single quotes stop the expansion. Also add -File: without it
Get-ChildItem yields directories too, and DirectoryInfo has no
IsReadOnly property, so those iterations would fail even once the
variables survive.

The POSIX branch above is unaffected - chmod needs no variables.

# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 15:40:22 +00:00
staticdevandReece Browne c3802ac1ef fix(frontend): remove Automate operation timeout (#7082)
# Description of Changes

Remove the hard-coded five-minute client-side timeout from Automate
operations.

- Remove `AUTOMATION_CONSTANTS.OPERATION_TIMEOUT`.
- Let normal Automate requests use the API client's default no-timeout
behavior, matching regular tool execution.
- Preserve an explicitly supplied `AutomationProcessingOptions.timeout`
without applying a finite default.
- Add regression coverage verifying that Automate does not send a
client-side timeout by default.

Large PDF operations such as compression can legitimately take more than
five minutes. Previously, Axios aborted the frontend request after
300,000 ms even when the server and reverse proxy allowed the operation
to continue. The backend could continue processing while the frontend
discarded the result.

No significant implementation challenges were encountered.

Closes #7081 

## Testing

The following frontend checks passed:

- Proprietary frontend TypeScript typecheck
- ESLint with zero warnings
- Circular dependency check
- Theme colour lint
- Prettier formatting check
- Focused `automationExecutor.test.ts` regression test
- Complete Vitest suite: 165 test files and 1,354 tests passed

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(not applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(not applicable; no user-facing configuration changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(not applicable)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)
(not applicable)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(not applicable; no visual changes)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.

Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
2026-08-12 15:30:53 +00:00
Ludy fdc1682863 chore: relocate frontend static assets and improve backend-only resource handling (#7171)
# Description of Changes

Move favicons, SVGs, and images from backend resources to frontend
public directory. Update Gradle build to copy sample files in
backend-only mode. Fix og-metadata image references for shared-sign
feature. Update .gitignore to exclude generated/frontend-managed assets
from backend.


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-12 15:27:20 +00:00
Ludy 10ed0f9f73 deps: upgrade Stripe frontend libraries to latest compatible versions (#7110)
# Description of Changes

## What was changed
- Upgraded `@stripe/react-stripe-js` from `4.0.2` to `6.8.0`.
- Upgraded `@stripe/stripe-js` from `7.9.0` to `9.10.0`.
- Updated `package-lock.json` to reflect the new dependency versions.

## Why the change was made
- Keep Stripe frontend dependencies up to date with the latest supported
releases.
- Align `@stripe/react-stripe-js` with its required peer dependency on
`@stripe/stripe-js` `>=9.5.0 <10.0.0`.
- Benefit from the latest bug fixes, improvements, and compatibility
updates provided by the Stripe SDKs.


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-12 15:18:41 +00:00
Ludy a28a950aa4 feat: modernize codebase using Java switch expressions and List#getFirst/getLast APIs (#6334)
# Description of Changes

This PR introduces a broad modernization of the codebase by adopting
newer Java language features and improving code readability and
maintainability.

## What was changed

- Replaced traditional `switch` statements with modern switch
expressions (`case ->`) across multiple classes.
- Replaced usages of `List#get(0)` and `List#get(size - 1)` with
`getFirst()` and `getLast()` respectively.
- Simplified conditional logic using pattern matching (e.g.,
`instanceof` and switch pattern matching).
- Refactored various utility and controller classes to reduce
boilerplate and improve clarity.
- Removed unused or redundant code (e.g., `parseClientFileIds` method in
`MergeController`).
- Improved type safety (e.g., using `Class::isInstance` instead of
`instanceof` checks in streams).
- Cleaned up Spring annotations by removing unnecessary `@Autowired`
where constructor injection is already used.
- Added a new test (`UIDataControllerTest`) to ensure correct handling
of identical JSON configs with different filenames.
- Minor formatting and style fixes (e.g., Spotless formatting
adjustment).

## Why the change was made

- To align the codebase with modern Java standards (Java 17+ features).
- To improve readability and maintainability by reducing verbosity.
- To eliminate common indexing patterns that are more error-prone.
- To standardize coding style across the project.
- To improve test coverage for edge cases discovered during refactoring.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.

---------

Signed-off-by: Ludy87 <Ludy87@users.noreply.github.com>
2026-08-12 15:13:29 +00:00
Ludy 84804d7ce3 fix(frontend): allow null users in mock API options (#7357)
# Description of Changes

Updated the frontend test helper type to allow `user: null` in
`MockAppApiOptions`.

This aligns the TypeScript definition with the existing test usage for
unauthenticated scenarios and resolves the frontend typecheck error in
`login-agreement-modal.spec.ts`.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-12 15:12:07 +00:00
Ludy 6d5d651432 chore(vscode): expand Java resource filters for workspace indexing (#7404)
Expand and reorganize java.project.resourceFilters in
.vscode/settings.json. Adds cache/build/bin/logs/storage exclusions
(app/core, app/common, app/proprietary, app/saas), frontend/engine,
docker and other generated/temp paths; removes duplicates and reorders
entries to reduce Java indexing of generated files and improve IDE
performance.


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-12 15:10:04 +00:00
dependabot[bot] be684942b7 build(deps): bump dompurify from 3.4.12 to 3.4.13 in /frontend (#7367)
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.4.12 to
3.4.13.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/cure53/DOMPurify/releases">dompurify's
releases</a>.</em></p>
<blockquote>
<h2>DOMPurify 3.4.13</h2>
<ul>
<li>Fixed an issue with hook removal during <code>IN_PLACE</code>
sanitization, thanks <a
href="https://github.com/koyokr"><code>@​koyokr</code></a></li>
<li>Fixed an issue with hooks potentially bypassing the clone guard,
thanks <a
href="https://github.com/AkshayjainG"><code>@​AkshayjainG</code></a></li>
<li>Fixed an issue with DOM clobbering via <code>ownerDocument</code>
during <code>IN_PLACE</code>, thanks <a
href="https://github.com/AkshayjainG"><code>@​AkshayjainG</code></a></li>
<li>Bumped several dependencies where possible</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/cure53/DOMPurify/commit/3067f774676975de12306effd6db6ad7a9a8c17f"><code>3067f77</code></a>
release: 3.4.13 (<a
href="https://redirect.github.com/cure53/DOMPurify/issues/1562">#1562</a>)</li>
<li>See full diff in <a
href="https://github.com/cure53/DOMPurify/compare/3.4.12...3.4.13">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dompurify&package-manager=npm_and_yarn&previous-version=3.4.12&new-version=3.4.13)](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)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/Stirling-Tools/Stirling-PDF/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-12 15:08:59 +00:00
Ludy 2b6bb71186 fix(build): ensure ./gradlew clean removes stale SaaS build artifacts (#7434)
### Motivation
- `./gradlew clean` on the default/proprietary flavor did not remove
`app/saas/build` left behind by earlier SaaS builds, causing stale
artifacts to persist across flavors.

### Description
- Add a `clean` hook in `build.gradle` that deletes `app/saas/build`
(`tasks.named('clean') { delete
layout.projectDirectory.dir('app/saas/build') }`) so the root `clean`
always removes SaaS artifacts even when `:saas` is not included.

### Testing
- Created `app/saas/build/clean-regression-marker`, ran `./gradlew
clean`, and verified the `app/saas/build` directory was deleted
(success).
- Ran `./gradlew spotlessCheck test`; Spotless checks passed but the
full test run reported environment-dependent unit test failures
unrelated to this change (8 failures), and `task backend:check` could
not be executed because the `task` CLI is not available in the
environment.

------
[Codex
Task](https://chatgpt.com/codex/cloud/tasks/task_e_6a7b0a9aac888325aa78e05c953355dc)
2026-08-12 15:06:03 +00:00
Ludy c47fb9032e chore(dependabot): group Docker updates and enable uv dependency management (#7403)
Organize Docker image updates into logical groups (ubuntu,
eclipse-temurin, gradle, uv) to reduce dependency update noise. Add uv
package manager support for the Python engine with weekly update
schedule.


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-12 15:05:59 +00:00
stirlingbot[bot] 8d0414acf3 Update Backend 3rd Party Licenses (#7401)
Auto-generated by stirlingbot[bot]

This PR updates the backend license report based on dependency changes.

Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2026-08-12 15:05:45 +00:00
Ludy 815c1db567 Group Storybook packages in Dependabot config (#7432)
### Motivation
- Group Storybook packages so Dependabot updates `storybook` and all
`@storybook/*` packages together and reduce churn from many separate
updates.

### Description
- Add a `storybook` group to the `npm` groups in
`.github/dependabot.yml` with patterns `storybook` and `@storybook/*`.
- No other configuration changes were made.

### Testing
- Validated the new group patterns with `ruby -e 'require "yaml"; ...'`,
which confirmed the `storybook` group and patterns and returned success.
- Ran `git diff --check` to ensure there are no whitespace or patch
errors, which succeeded.
- Inspected the updated file with `nl -ba .github/dependabot.yml | sed
-n '100,120p'` to confirm the inserted lines are present.

------
[Codex
Task](https://chatgpt.com/codex/cloud/tasks/task_e_6a7afe74fe7083259900e8d9018f595c)
2026-08-12 15:04:24 +00:00
Ludy ea8fc5ae52 chore: configure repository editor settings (#7411)
## Description

Moves the repository-wide VS Code settings out of PR #7386 into a
dedicated pull request.

## Changes

- Configure Ruff for Python files and point it at the engine project
configuration.
- Add repository-root paths for stylelint and CSS/SCSS/Less at-rule
handling.
- Enable Oxc safe fixes on save for JavaScript and TypeScript files.
- Add Even Better TOML formatter settings for the repository's TOML
files.

This keeps PR #7386 focused on the Python tooling migration while
preserving the editor configuration as a separately reviewable change.

## Validation

- Exact file diff matches the settings change from PR #7386.
- `task pre-commit:whitespace` was attempted; it is currently blocked by
pre-existing whitespace issues in
`engine/src/stirling/models/tool_io.py` and
`engine/src/stirling/models/tool_models.py`.
2026-08-12 15:04:12 +00:00
Anthony Stirling 7a748d4ad2 Add stored supporting files for pipeline steps (#7146)
# Description of Changes

Backend only change for pipelines to support files (ie pipeline to sign
all files with the same cert file etc)

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-12 15:02:10 +00:00
Anthony Stirling dc75d399bc Editor url role routing (#7409)
# Description of Changes

- `/` is now a router, not a page: signed-in users go to the processor
or the editor by role. The editor lives at `/editor`.
- `/editor` never routes — always the editor, so processor users have a
URL that won't bounce them.
- Core and desktop keep the editor at `/` (no processor, nothing to
route between).
- `/editor` signed out → `/login` → back to `/editor` after signing in.
- Signed-out visitors aren't redirected: `/` renders the app and Landing
owns it (login page / SaaS inline sign-in / backend-down screen).
- `RootGate` wraps the app instead of being its own route, so nothing
boots on the way to the processor and nothing remounts on the way to the
editor.
- Login resolves its own destination instead of bouncing through `/`.
- Replaces the old once-per-login `LoginLandingRedirect` +
sessionStorage flag. Landing flag and Settings preference unchanged.
- Separate commit: theme-lint crashed on files deleted in the working
tree (`git ls-files` is the index view). Any branch deleting a source
file hit it.
- Sign-out untouched. Tool routes stay top-level, so no deep links or
SEO break.


Future PR to allow users to configure their own routing from / for their
profile

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-12 10:09:18 +00:00
Reece Browne c2e8c3fa71 a11y: empty the grandfathered Storybook baseline (1,058 → 0) (#7309)
## What

Empties the light Storybook accessibility baseline — **1,058
grandfathered violations across 846 stories → 0** — so a new violation
fails the gate instead of being silently absorbed. Also burns the dark
baseline **812 → 56**; every entry left is one `main` already
grandfathers.

## The defect, repeated everywhere

A colour picked as a **fill**, chosen to carry a white label at 3:1,
reused as **text**, where the floor is 4.5:1. It recurred through status
accents, filled buttons, form labels, Mantine's light and outline
variants, CSS declarations, inline styles and the generated accent ramp.

Three systemic causes account for most of it:

- **Mantine's semantic slots were never bound.** `-text`, `-outline`,
`-light-color`, `-filled` and `-dimmed` all default to the hue's solid
fill. Both resolvers now pin them to the accessible ink for the active
scheme.
- **The tint ladder was compressed.** `--color-<hue>-50/100/200` pointed
at saturated 400-level primitives, so every "tint" background rendered
as a fill.
- **Text was faded with `opacity`**, pushing already-muted copy below
the floor. Each site now recedes via ink or surface, which is what
conveyed the state anyway.

## Dark mode

The colour resolver's dark half was empty, so dark fell through to
Mantine's stock palette — and fixing the naming violations unmasked the
contrast sitting underneath them. Both schemes now share one slot map,
since most slots are written in tokens that already flip.

The dark-only fixes: `--c-text-subtle` (3.0:1, used in 478 places), the
error and section-label inks, and the accent ramp's text step — which
light reaches by mixing toward black and dark has to reach by mixing
toward white.

## Also

- New `--c-*-solid` tokens for fills that must carry a white label,
distinct from the `--c-<tone>` values used for surfaces, borders and
icons.
- A `data-user-content-preview` opt-out for nodes rendering a facsimile
of the user's own document — WCAG governs the interface, not content
authored through it.

## Verification

- `task frontend:check:all` — green.
- Changed-set gate, both schemes, after the final rebase: **366 stories,
0 regressions**.
- Full sweep at the prior base — light **1,447 stories / 0 violations**,
dark **1,448 / 0 regressions**. The dark re-record was confirmed
key-by-key to be a strict subset of `main`'s, so nothing new is
grandfathered.

Roughly 28% of what this clears is naming and structure (`button-name`,
`label`, `aria-*`) and has no visual signature; the rest is contrast.
2026-08-12 09:06:05 +00:00
Reece Browne 34819ae502 Draw signatures on a phone via QR code in the Sign tool (#7335)
# Description of Changes

Scan a QR code in the Sign tool, draw your signature on your phone, and
it appears on your desktop ready to place. Rides the mobile scanner's
existing transfer sessions — no new backend endpoints.

**Desktop:** a **Mobile upload** button above the signature source
selector shows a QR code. When the signature arrives, the modal closes,
it lands in the matching source, and **placement activates
automatically** — click the PDF to place.

**Phone:** a new public `/mobile-sign` page with three tabs (same order
as the desktop sources):

- **Draw** → canvas signature. Touch-first pad (pointer events,
DPR-aware, smoothed strokes, undo/clear, black/blue ink, 3 pen sizes),
exported as a transparent PNG cropped to the ink. Compact layout in
phone landscape.
- **Photo** → image signature. "Take a photo" opens the camera directly;
"From gallery" opens the picker. A preview of the current image
signature now shows in the desktop's Image source (previously arrival
was invisible until placement — also fixes this for saved image
signatures).
- **Type** → text signature. Travels as data (text + font + colour), so
it stays *editable* on the desktop. Fonts are the sign tool's own
text-mode list.

**Security:** the transfer endpoints are unauthenticated by design
(10-min sessions, files deleted after download — same model as the
scanner). The desktop treats every arrival as untrusted: images only,
and the text payload is clamped field by field.

**Config:** new `system.enableMobileSignature` flag (default on),
independent of `enableMobileScanner`; the shared endpoints accept
either. The Tauri desktop app serves a self-contained `mobile-sign.html`
(draw-only), mirroring `mobile-upload.html`.

**Refactor:** the session lifecycle (create/poll/download/expiry) moved
out of `MobileUploadModal` into a shared `useMobileTransferSession`
hook; the scanner modal now uses it, behaviour unchanged.

Also fixes two bugs hit along the way: the signature pad collapsing to
its 150px intrinsic height (indefinite parent height), and a setState
loop in `SignSettings` when text parameters are set programmatically
(draft-sync effects ping-ponging).

## Screenshots

| Desktop: QR entry | Phone: draw | Desktop: received |
|---|---|---|
| ![QR
modal](https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/pr-7335-assets/shot-1-qr-modal.png)
| ![Phone draw
tab](https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/pr-7335-assets/shot-2-phone-draw.png)
| ![Signature
received](https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/pr-7335-assets/shot-3-desktop-received.png)
|

## How to test

1. Open the app on an address your phone can reach (not `localhost`),
Sign tool → **Mobile upload**, scan the QR.
2. Draw → **Send to computer** → it becomes the active canvas signature
and placement is live: click the PDF to place.
3. Photo tab → arrives in the Image source with a preview. Type tab →
arrives editable in the Text source.
4. Flags: `enableMobileSignature: false` hides the button; signature
still works with the scanner disabled.

Verified end-to-end (all three kinds, portrait/landscape/tablet) plus
`task frontend:check` and the touched backend tests.

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [x] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [x] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [x] I have run `task check` to verify linters, typechecks, and tests
pass
- [x] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-11 16:36:51 +00:00
1123 changed files with 60585 additions and 35070 deletions
+7
View File
@@ -98,6 +98,13 @@ body:
description: |
If you have any additional information that might help us understand and resolve the issue, provide it here.
- type: textarea
id: sample-files
attributes:
label: Sample Files
description: |
If possible, attach the PDF or other input files needed to reproduce the issue. Remove any sensitive information before sharing.
- type: markdown
attributes:
value: |
+7
View File
@@ -67,6 +67,13 @@ body:
description: |
If you have any additional information, comments, or resources you think would support or be relevant to your feature request, include them here.
- type: textarea
id: sample-files
attributes:
label: Example Files
description: |
If the feature request depends on specific PDFs or other example files, attach them here when available. Remove any sensitive information before sharing.
- type: checkboxes
id: search-confirmation
attributes:
+1 -1
View File
@@ -1,6 +1,6 @@
# Maintainer: Stirling PDF Inc <contact@stirlingpdf.com>
pkgname=stirling-pdf-desktop
pkgver=2.14.2
pkgver=2.14.3
pkgrel=1
pkgdesc="Locally hosted, web-based PDF manipulation tool (Tauri desktop app, official Stirling PDF Inc build)"
arch=('x86_64')
+1 -1
View File
@@ -1,6 +1,6 @@
# Maintainer: Stirling PDF Inc <contact@stirlingpdf.com>
pkgname=stirling-pdf-server-bin
pkgver=2.14.2
pkgver=2.14.3
pkgrel=1
pkgdesc="Locally hosted, web-based PDF manipulation tool (server JAR, prebuilt)"
arch=('any')
+36 -19
View File
@@ -1,13 +1,15 @@
# CI routing infra. Editing the top-level router (build.yml) or this filter
# config re-runs every area's jobs, so every job-gating filter below includes
# *ci. That makes a change to how jobs are dispatched actually exercise those
# jobs (self-testing), instead of a router edit only matching the project filter.
# CI routing infrastructure. Changes to the top-level router (build.yml) or
# this filter configuration rerun every area's jobs. Every job-gating filter
# therefore includes *ci, so routing changes exercise the jobs they affect
# instead of matching only the project filter.
ci: &ci
- .github/workflows/build.yml
- .github/workflows/gradle-cache-prime.yml
- .github/config/.files.yaml
build: &build
- *ci
- buildSrc/**
- build.gradle
- gradle/spotless.gradle
- app/(common|core|proprietary|saas)/build.gradle
@@ -15,6 +17,22 @@ build: &build
- .taskfiles/backend.yml
- .github/workflows/check-licence.yml
# Backend build inputs. This is intentionally broader than `build`: Java and
# backend resource changes must exercise the backend matrix even when Gradle
# build scripts themselves are unchanged.
backend: &backend
- *ci
- *build
- gradle/**
- gradle.properties
- gradlew
- gradlew.bat
- settings.gradle
- app/(common|core|proprietary|saas)/src/(main|test)/java/**
- "app/(common|core|proprietary|saas)/src/(main|test)/resources/**/!(messages_*.properties|*.md)*"
- scripts/db-migration/**
- .github/workflows/backend-build.yml
openapi: &openapi
- *ci
- *build
@@ -24,9 +42,9 @@ openapi: &openapi
docker-base: &docker-base
- docker/base/Dockerfile
# Dockerfiles only (base + embedded + unoserver). Gates the slow multi-arch
# (arm64) leg of the PR docker test build: arm64 is only rebuilt when a
# Dockerfile itself changes, not on every code PR.
# Dockerfiles only (base, embedded, and unoserver). The slow multi-architecture
# (arm64) leg of the PR Docker test build runs only when a Dockerfile changes,
# rather than for every code PR.
dockerfiles: &dockerfiles
- docker/**/Dockerfile*
@@ -68,7 +86,6 @@ project: &project
frontend: &frontend
- *ci
- frontend/**
- .github/workflows/testdriver.yml
- testing/**
- docker/**
- scripts/translations/*.py
@@ -88,8 +105,8 @@ frontend: &frontend
- .github/workflows/e2e-stubbed.yml
- .github/workflows/e2e-live.yml
# Files that affect the Tauri desktop bundle. Gate the multi-OS Tauri build
# job on changes to any of these.
# Files that affect the Tauri desktop bundle. Changes to any of these files
# trigger the multi-OS Tauri build job.
tauri: &tauri
- *ci
- frontend/editor/src-tauri/**
@@ -102,9 +119,9 @@ tauri: &tauri
- Taskfile.yml
- .taskfiles/desktop.yml
# Files that affect the AI engine (Python tool models, fixers, tests). Gate
# the engine validation job on changes to engine sources or to the Java
# tool surfaces it generates models from.
# Files that affect the AI engine, including its Python tool models, fixers,
# and tests. The engine validation job also runs when the Java tool surfaces
# used to generate those models change.
engine: &engine
- *ci
- engine/**
@@ -114,10 +131,10 @@ engine: &engine
- .taskfiles/engine.yml
# Files that can make the committed generated API models (frontend tool API
# types + engine tool models) go stale: the Java tool surfaces they derive from,
# the generators, the generated files themselves (to catch a hand-edit), and the
# tasks that drive generation. Deliberately excludes the broad frontend/docker/
# testing globs, so a CSS-only PR does not boot the backend to rebuild the spec.
# types and engine tool models) stale: their Java sources, generators,
# generated outputs (to catch hand edits), and generation tasks. Broad
# frontend, Docker, and testing globs are intentionally excluded, so a CSS-only
# PR does not start the backend to rebuild the specification.
generated-models: &generated-models
- *ci
- *openapi
@@ -141,8 +158,8 @@ licenses-backend: &licenses-backend
- ".github/workflows/frontend-backend-licenses-update.yml"
- *build
# Files that can affect premium / enterprise behaviour. Gate the enterprise
# Playwright job on changes to any of these on PRs.
# Files that can affect premium or enterprise behaviour. Changes to any of
# these files trigger the enterprise Playwright job for pull requests.
proprietary: &proprietary
- *ci
- app/proprietary/**
+29 -2
View File
@@ -11,6 +11,8 @@ updates:
- "/app/common"
- "/app/core"
- "/app/proprietary"
- "/app/saas"
- "/buildSrc"
schedule:
interval: "weekly"
cooldown:
@@ -37,6 +39,19 @@ updates:
cooldown:
default-days: 7
rebase-strategy: "auto"
groups:
ubuntu:
patterns:
- "ubuntu"
eclipse-temurin:
patterns:
- "eclipse-temurin"
uv:
patterns:
- "ghcr.io/astral-sh/uv"
gradle:
patterns:
- "gradle"
- package-ecosystem: github-actions
directory: /
@@ -50,6 +65,7 @@ updates:
directories:
- /devTools
- /frontend
- /testing/compose/mcp-client-check
schedule:
interval: "weekly"
cooldown:
@@ -78,6 +94,13 @@ updates:
- "react-dom"
- "@types/react"
- "@types/react-dom"
tanstack:
patterns:
- "@tanstack/*"
typescript:
patterns:
- "typescript"
- "@typescript/*"
vite:
patterns:
- "vite"
@@ -106,6 +129,10 @@ updates:
patterns:
- "@posthog/*"
- "posthog-js"
storybook:
patterns:
- "storybook"
- "@storybook/*"
supabase:
patterns:
- "@supabase/*"
@@ -152,8 +179,8 @@ updates:
- "tokio"
- "tokio-*"
- package-ecosystem: pip
directory: /testing/cucumber
- package-ecosystem: "uv"
directory: "/engine"
schedule:
interval: "weekly"
cooldown:
+56 -41
View File
@@ -26,6 +26,10 @@ jobs:
check-pr:
if: (github.event_name == 'pull_request' && github.event.action != 'closed') || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
# Only reads the PR via pulls.get with the default GITHUB_TOKEN.
permissions:
contents: read
pull-requests: read
outputs:
should_deploy: ${{ steps.decide.outputs.should_deploy }}
is_fork: ${{ steps.resolve.outputs.is_fork }}
@@ -35,7 +39,7 @@ jobs:
pr_ref: ${{ steps.resolve.outputs.ref }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
@@ -97,6 +101,7 @@ jobs:
echo "allow_fork=${allow_fork:-false}" >> $GITHUB_OUTPUT
deploy-v2-pr:
environment: pr-preview
needs: check-pr
runs-on: ubuntu-latest
if: needs.check-pr.outputs.should_deploy == 'true' && (needs.check-pr.outputs.is_fork == 'false' || needs.check-pr.outputs.allow_fork == 'true')
@@ -107,6 +112,7 @@ jobs:
permissions:
contents: read
issues: write
packages: write
pull-requests: write
env:
# Single source of truth for whether this preview embeds the admin portal:
@@ -115,7 +121,7 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
@@ -125,20 +131,11 @@ jobs:
repository: ${{ github.repository }}
ref: main
- name: Setup GitHub App Bot
if: github.actor != 'dependabot[bot]'
id: setup-bot
uses: ./.github/actions/setup-bot
continue-on-error: true
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Add deployment started comment
id: deployment-started
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
const { owner, repo } = context.repo;
const prNumber = ${{ needs.check-pr.outputs.pr_number }};
@@ -180,7 +177,8 @@ jobs:
with:
repository: ${{ needs.check-pr.outputs.pr_repository }}
ref: ${{ needs.check-pr.outputs.pr_ref }}
token: ${{ secrets.GITHUB_TOKEN }}
# untrusted tree is built below - never leave credentials in .git/config
persist-credentials: false
fetch-depth: 0 # Fetch full history for commit hash detection
- name: Set up Docker Buildx
@@ -192,11 +190,16 @@ jobs:
VERSION=$(grep "^version =" build.gradle | awk -F'"' '{print $2}')
echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
- name: Login to Docker Hub
- name: Login to GitHub Container Registry
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Convert repository owner to lowercase
id: repoowner
run: echo "lowercase=$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')" >> $GITHUB_OUTPUT
- name: Get commit hash for app
id: commit-hash
@@ -220,7 +223,7 @@ jobs:
- name: Check if image exists
id: check-image
run: |
if docker manifest inspect ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-${{ steps.commit-hash.outputs.app_short }} >/dev/null 2>&1; then
if docker manifest inspect ${IMAGE_BASE}:v2-${{ steps.commit-hash.outputs.app_short }} >/dev/null 2>&1; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "Image already exists, skipping build"
else
@@ -228,6 +231,8 @@ jobs:
echo "Image needs to be built"
fi
env:
IMAGE_BASE: ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test
- name: Build and push V2 image
if: steps.check-image.outputs.exists == 'false'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
@@ -237,7 +242,7 @@ jobs:
push: true
cache-from: type=gha,scope=stirling-pdf-latest
cache-to: type=gha,mode=max,scope=stirling-pdf-latest
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-${{ steps.commit-hash.outputs.app_short }}
tags: ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:v2-${{ steps.commit-hash.outputs.app_short }}
build-args: |
VERSION_TAG=v2-alpha
BUILD_PORTAL=${{ env.BUILD_PORTAL }}
@@ -246,9 +251,11 @@ jobs:
- name: Set up SSH
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.NEW_VPS_SSH_KEY }}" > ../private.key
echo "${NEW_VPS_SSH_KEY}" > ../private.key
sudo chmod 600 ../private.key
env:
NEW_VPS_SSH_KEY: ${{ secrets.NEW_VPS_SSH_KEY }}
- name: Deploy V2 to VPS
id: deploy
run: |
@@ -261,7 +268,7 @@ jobs:
services:
stirling-pdf-v2:
container_name: stirling-pdf-v2-pr-${{ needs.check-pr.outputs.pr_number }}
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-${{ steps.commit-hash.outputs.app_short }}
image: ${IMAGE_BASE}:v2-${{ steps.commit-hash.outputs.app_short }}
ports:
- "${V2_PORT}:8080"
volumes:
@@ -273,8 +280,8 @@ jobs:
DISABLE_ADDITIONAL_FEATURES: "false"
STIRLING_BILLING_ACCOUNT_LINK_ENABLED: "true"
SECURITY_ENABLELOGIN: "true"
SECURITY_INITIALLOGIN_USERNAME: "${{ secrets.TEST_LOGIN_USERNAME }}"
SECURITY_INITIALLOGIN_PASSWORD: "${{ secrets.TEST_LOGIN_PASSWORD }}"
SECURITY_INITIALLOGIN_USERNAME: "${TEST_LOGIN_USERNAME}"
SECURITY_INITIALLOGIN_PASSWORD: "${TEST_LOGIN_PASSWORD}"
SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: "Stirling-PDF V2 PR#${{ needs.check-pr.outputs.pr_number }}"
UI_HOMEDESCRIPTION: "V2 PR#${{ needs.check-pr.outputs.pr_number }} - Embedded Architecture"
@@ -288,9 +295,9 @@ jobs:
EOF
# Deploy to VPS
scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null docker-compose.yml ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }}:/tmp/docker-compose-v2.yml
scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null docker-compose.yml ${NEW_VPS_USERNAME}@${NEW_VPS_HOST}:/tmp/docker-compose-v2.yml
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }} << ENDSSH
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${NEW_VPS_USERNAME}@${NEW_VPS_HOST} << ENDSSH
# Create V2 PR-specific directories
mkdir -p /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/{data,config,logs,storage}
@@ -315,12 +322,19 @@ jobs:
# Set port for output
echo "v2_port=${V2_PORT}" >> $GITHUB_OUTPUT
env:
IMAGE_BASE: ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test
TEST_LOGIN_USERNAME: ${{ secrets.TEST_LOGIN_USERNAME }}
TEST_LOGIN_PASSWORD: ${{ secrets.TEST_LOGIN_PASSWORD }}
NEW_VPS_USERNAME: ${{ secrets.NEW_VPS_USERNAME }}
NEW_VPS_HOST: ${{ secrets.NEW_VPS_HOST }}
# ---- Storybook preview (only when this PR touches stories/.storybook) ----
# Runs inside the same approved-contributor-gated deploy job, so it deploys
# under the exact same access rules as the app preview.
- name: Detect Storybook changes
id: sb-changes
uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
with:
list-files: json
filters: |
@@ -379,8 +393,9 @@ jobs:
env:
SB_URL: ${{ steps.storybook.outputs.url }}
SB_FILES: ${{ steps.sb-changes.outputs.storybook_files }}
NEW_VPS_HOST: ${{ secrets.NEW_VPS_HOST }}
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
const { owner, repo } = context.repo;
const prNumber = ${{ needs.check-pr.outputs.pr_number }};
@@ -401,7 +416,7 @@ jobs:
}
}
const deploymentUrl = `http://${{ secrets.NEW_VPS_HOST }}:${v2Port}`;
const deploymentUrl = `http://${process.env.NEW_VPS_HOST}:${v2Port}`;
// Only mention the portal when this image actually embeds it.
// Use the direct IP URL - the SSL hostname isn't supported yet.
@@ -447,6 +462,10 @@ jobs:
});
cleanup-v2-deployment:
# Tearing a preview down is not a deployment - no deployment object.
environment:
name: pr-preview
deployment: false
if: github.event.action == 'closed'
runs-on: ubuntu-latest
permissions:
@@ -456,26 +475,17 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup GitHub App Bot
if: github.actor != 'dependabot[bot]'
id: setup-bot
uses: ./.github/actions/setup-bot
continue-on-error: true
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Clean up V2 deployment comments
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
const { owner, repo } = context.repo;
const prNumber = ${{ github.event.pull_request.number }};
@@ -504,12 +514,14 @@ jobs:
- name: Set up SSH
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.NEW_VPS_SSH_KEY }}" > ../private.key
echo "${NEW_VPS_SSH_KEY}" > ../private.key
sudo chmod 600 ../private.key
env:
NEW_VPS_SSH_KEY: ${{ secrets.NEW_VPS_SSH_KEY }}
- name: Cleanup V2 deployment
run: |
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }} << 'ENDSSH'
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${NEW_VPS_USERNAME}@${NEW_VPS_HOST} << 'ENDSSH'
if [ -d "/stirling/V2-PR-${{ github.event.pull_request.number }}" ]; then
echo "Found V2 PR directory, proceeding with cleanup..."
@@ -542,8 +554,11 @@ jobs:
# Only remove PR-specific containers and directories
ENDSSH
env:
NEW_VPS_USERNAME: ${{ secrets.NEW_VPS_USERNAME }}
NEW_VPS_HOST: ${{ secrets.NEW_VPS_HOST }}
- name: Cleanup temporary files
if: always()
run: |
rm -f ../private.key
rm -f ../private.key docker-compose.yml storybook.tgz
continue-on-error: true
@@ -37,7 +37,8 @@ jobs:
check-comment:
runs-on: ubuntu-latest
permissions:
issues: write
contents: read # actions/checkout
issues: write # add reaction to the triggering issue comment
if: |
vars.CI_PROFILE != 'lite' && (
github.event_name == 'workflow_dispatch' ||
@@ -69,22 +70,13 @@ jobs:
enable_prototypes: ${{ steps.check-prototypes-flag.outputs.enable_prototypes }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout PR
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup GitHub App Bot
if: github.actor != 'dependabot[bot]'
id: setup-bot
uses: ./.github/actions/setup-bot
continue-on-error: true
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Get PR data
id: get-pr
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -155,7 +147,7 @@ jobs:
id: add-eyes-reaction
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
console.log(`Adding eyes reaction to comment ID: ${context.payload.comment.id}`);
try {
@@ -174,35 +166,38 @@ jobs:
}
deploy-pr:
environment: pr-preview
needs: check-comment
runs-on: ubuntu-latest
permissions:
issues: write
contents: read # actions/checkout, incl. the PR merge ref
issues: write # reactions, 'pr-deployed' label, deployment URL comment
pull-requests: write
packages: write # push PR image to ghcr.io
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout PR
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup GitHub App Bot
if: github.actor != 'dependabot[bot]'
id: setup-bot
uses: ./.github/actions/setup-bot
continue-on-error: true
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Checkout PR
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: refs/pull/${{ needs.check-comment.outputs.pr_number }}/merge
token: ${{ steps.setup-bot.outputs.token }}
# untrusted tree gets built below - never leave credentials in .git/config
persist-credentials: false
- name: Cache Gradle
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-deploy-pr-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
@@ -210,17 +205,6 @@ jobs:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
- name: Run Gradle Command
@@ -240,11 +224,16 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Login to Docker Hub
- name: Login to GitHub Container Registry
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Convert repository owner to lowercase
id: repoowner
run: echo "lowercase=$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')" >> $GITHUB_OUTPUT
- name: Build and push PR-specific image
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
@@ -254,7 +243,7 @@ jobs:
push: true
cache-from: type=gha,scope=stirling-pdf-latest
cache-to: type=gha,mode=max,scope=stirling-pdf-latest
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:pr-${{ needs.check-comment.outputs.pr_number }}
tags: ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:pr-${{ needs.check-comment.outputs.pr_number }}
build-args: |
VERSION_TAG=alpha
PROTOTYPES_BUILD=${{ needs.check-comment.outputs.enable_prototypes }}
@@ -269,15 +258,17 @@ jobs:
push: true
cache-from: type=gha,scope=stirling-pdf-engine
cache-to: type=gha,mode=max,scope=stirling-pdf-engine
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:engine-pr-${{ needs.check-comment.outputs.pr_number }}
tags: ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:engine-pr-${{ needs.check-comment.outputs.pr_number }}
platforms: linux/amd64
- name: Set up SSH
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.NEW_VPS_SSH_KEY }}" > ../private.key
echo "${NEW_VPS_SSH_KEY}" > ../private.key
sudo chmod 600 ../private.key
env:
NEW_VPS_SSH_KEY: ${{ secrets.NEW_VPS_SSH_KEY }}
- name: Deploy to VPS
id: deploy
run: |
@@ -295,11 +286,11 @@ jobs:
# Set pro/enterprise settings (enterprise implies pro)
if [ "${{ needs.check-comment.outputs.enable_enterprise }}" == "true" ]; then
PREMIUM_ENABLED="true"
PREMIUM_KEY="${{ secrets.ENTERPRISE_KEY }}"
PREMIUM_KEY="${ENTERPRISE_KEY}"
PREMIUM_PROFEATURES_AUDIT_ENABLED="true"
elif [ "${{ needs.check-comment.outputs.enable_pro }}" == "true" ]; then
PREMIUM_ENABLED="true"
PREMIUM_KEY="${{ secrets.PREMIUM_KEY }}"
PREMIUM_KEY="${PRO_KEY}"
PREMIUM_PROFEATURES_AUDIT_ENABLED="true"
else
PREMIUM_ENABLED="false"
@@ -309,7 +300,6 @@ jobs:
ENABLE_PROTOTYPES="${{ needs.check-comment.outputs.enable_prototypes }}"
PR_NUMBER="${{ needs.check-comment.outputs.pr_number }}"
DOCKER_USER="${{ secrets.DOCKER_HUB_USERNAME }}"
# Build engine env vars for backend (only set when prototypes enabled)
if [ "$ENABLE_PROTOTYPES" == "true" ]; then
@@ -319,9 +309,9 @@ jobs:
ENGINE_SERVICE="
stirling-pdf-engine:
container_name: stirling-pdf-engine-pr-${PR_NUMBER}
image: ${DOCKER_USER}/test:engine-pr-${PR_NUMBER}
image: ${IMAGE_BASE}:engine-pr-${PR_NUMBER}
environment:
ANTHROPIC_API_KEY: \"${{ secrets.ANTHROPIC_API_KEY }}\"
ANTHROPIC_API_KEY: \"${ANTHROPIC_API_KEY}\"
networks:
- pr-network
restart: on-failure:5"
@@ -344,7 +334,7 @@ jobs:
services:
stirling-pdf:
container_name: stirling-pdf-pr-${PR_NUMBER}
image: ${DOCKER_USER}/test:pr-${PR_NUMBER}
image: ${IMAGE_BASE}:pr-${PR_NUMBER}
ports:
- "${PR_NUMBER}:8080"
volumes:
@@ -368,9 +358,9 @@ jobs:
EOF
# Then copy the file and execute commands
scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null docker-compose.yml ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }}:/tmp/docker-compose.yml
scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null docker-compose.yml ${NEW_VPS_USERNAME}@${NEW_VPS_HOST}:/tmp/docker-compose.yml
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }} << ENDSSH
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${NEW_VPS_USERNAME}@${NEW_VPS_HOST} << ENDSSH
# Create PR-specific directories
mkdir -p /stirling/PR-${PR_NUMBER}/{data,config,logs}
@@ -386,11 +376,19 @@ jobs:
# Set output for use in PR comment
echo "security_status=${SECURITY_STATUS}" >> $GITHUB_ENV
env:
ENTERPRISE_KEY: ${{ secrets.ENTERPRISE_KEY }}
# named PRO_KEY, not PREMIUM_KEY, so the shell var it feeds is not self-referential
PRO_KEY: ${{ secrets.PREMIUM_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
IMAGE_BASE: ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test
NEW_VPS_USERNAME: ${{ secrets.NEW_VPS_USERNAME }}
NEW_VPS_HOST: ${{ secrets.NEW_VPS_HOST }}
- name: Add success reaction to comment
if: success() && github.event_name == 'issue_comment'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
console.log(`Adding rocket reaction to comment ID: ${{ needs.check-comment.outputs.comment_id }}`);
try {
@@ -425,7 +423,7 @@ jobs:
if: failure() && github.event_name == 'issue_comment'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
console.log(`Adding -1 reaction to comment ID: ${{ needs.check-comment.outputs.comment_id }}`);
try {
@@ -444,15 +442,17 @@ jobs:
- name: Post deployment URL to PR
if: success()
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
NEW_VPS_HOST: ${{ secrets.NEW_VPS_HOST }}
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
const { GITHUB_REPOSITORY } = process.env;
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
const prNumber = ${{ needs.check-comment.outputs.pr_number }};
const securityStatus = process.env.security_status || "Security Disabled";
const deploymentUrl = `http://${{ secrets.NEW_VPS_HOST }}:${prNumber}`;
const deploymentUrl = `http://${process.env.NEW_VPS_HOST}:${prNumber}`;
const commentBody = `## 🚀 PR Test Deployment\n\n` +
`Your PR has been deployed for testing!\n\n` +
`🔗 **Test URL:** [${deploymentUrl}](${deploymentUrl})\n` +
@@ -477,26 +477,22 @@ jobs:
handle-label-commands:
if: ${{ github.event.issue.pull_request != null }}
runs-on: ubuntu-latest
permissions:
contents: read # actions/checkout, reads repo_devs.json and labels.yml
issues: write # add/remove labels, delete the command comment
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Check out the repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup GitHub App Bot
id: setup-bot
uses: ./.github/actions/setup-bot
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Apply label commands
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
const fs = require('fs');
const path = require('path');
+23 -20
View File
@@ -7,41 +7,33 @@ on:
permissions:
contents: read
env:
SERVER_IP: ${{ secrets.NEW_VPS_IP }} # Add this to your GitHub secrets
CLEANUP_PERFORMED: "false" # Add flag to track if cleanup occurred
jobs:
cleanup:
# Tearing a preview down is not a deployment - no deployment object.
environment:
name: pr-preview
deployment: false
if: github.event.action == 'closed'
runs-on: ubuntu-latest
permissions:
contents: read # actions/checkout
pull-requests: write
issues: write
issues: write # list/remove labels, list/delete comments
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout PR
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup GitHub App Bot
if: github.actor != 'dependabot[bot]'
id: setup-bot
uses: ./.github/actions/setup-bot
continue-on-error: true
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Remove 'pr-deployed' label if present
id: remove-label-comment
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
const prNumber = ${{ github.event.pull_request.number }};
const owner = context.repo.owner;
@@ -100,14 +92,22 @@ jobs:
if: steps.remove-label-comment.outputs.present == 'true'
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.NEW_VPS_SSH_KEY }}" > ../private.key
echo "${NEW_VPS_SSH_KEY}" > ../private.key
sudo chmod 600 ../private.key
env:
NEW_VPS_SSH_KEY: ${{ secrets.NEW_VPS_SSH_KEY }}
- name: Convert repository owner to lowercase
id: repoowner
run: echo "lowercase=$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')" >> $GITHUB_OUTPUT
- name: Cleanup PR deployment
if: steps.remove-label-comment.outputs.present == 'true'
id: cleanup
# ENDSSH heredoc is quoted, so its body is sent literally: secrets inside it
# must stay as GitHub expressions, a shell var would be empty on the remote host.
run: |
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }} << 'ENDSSH'
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${NEW_VPS_USERNAME}@${NEW_VPS_HOST} << 'ENDSSH'
if [ -d "/stirling/PR-${{ github.event.pull_request.number }}" ]; then
echo "Found PR directory, proceeding with cleanup..."
@@ -122,8 +122,8 @@ jobs:
rm -rf /stirling/PR-${{ github.event.pull_request.number }}
# Remove the Docker images
docker rmi --no-prune ${{ secrets.DOCKER_HUB_USERNAME }}/test:pr-${{ github.event.pull_request.number }} || true
docker rmi --no-prune ${{ secrets.DOCKER_HUB_USERNAME }}/test:engine-pr-${{ github.event.pull_request.number }} || true
docker rmi --no-prune ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:pr-${{ github.event.pull_request.number }} || true
docker rmi --no-prune ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:engine-pr-${{ github.event.pull_request.number }} || true
echo "PERFORMED_CLEANUP"
else
@@ -131,6 +131,9 @@ jobs:
echo "NO_CLEANUP_NEEDED"
fi
ENDSSH
env:
NEW_VPS_USERNAME: ${{ secrets.NEW_VPS_USERNAME }}
NEW_VPS_HOST: ${{ secrets.NEW_VPS_HOST }}
- name: Cleanup temporary files
if: always()
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
is_fork: ${{ steps.decide.outputs.is_fork }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
pull-requests: write
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
-228
View File
@@ -1,228 +0,0 @@
name: AI - PR Title Review
on:
pull_request:
types: [opened, edited]
branches: [main]
permissions: # required for secure-repo hardening
contents: read
jobs:
ai-title-review:
permissions:
contents: read
pull-requests: write
models: read
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Configure Git to suppress detached HEAD warning
run: git config --global advice.detachedHead false
- name: Setup GitHub App Bot
if: github.actor != 'dependabot[bot]'
id: setup-bot
uses: ./.github/actions/setup-bot
continue-on-error: true
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Check if actor is repo developer
id: actor
run: |
if [[ "${{ github.actor }}" == *"[bot]" ]]; then
echo "PR opened by a bot skipping AI title review."
echo "is_repo_dev=false" >> $GITHUB_OUTPUT
exit 0
fi
if [ ! -f .github/config/repo_devs.json ]; then
echo "Error: .github/config/repo_devs.json not found" >&2
exit 1
fi
# Validate JSON and extract repo_devs
REPO_DEVS=$(jq -r '.repo_devs[]' .github/config/repo_devs.json 2>/dev/null || { echo "Error: Invalid JSON in repo_devs.json" >&2; exit 1; })
# Convert developer list into Bash array
mapfile -t DEVS_ARRAY <<< "$REPO_DEVS"
if [[ " ${DEVS_ARRAY[*]} " == *" ${{ github.actor }} "* ]]; then
echo "is_repo_dev=true" >> $GITHUB_OUTPUT
else
echo "is_repo_dev=false" >> $GITHUB_OUTPUT
fi
- name: Get PR diff
if: steps.actor.outputs.is_repo_dev == 'true'
id: get_diff
run: |
git fetch origin ${{ github.base_ref }}
git diff origin/${{ github.base_ref }}...HEAD | head -n 10000 | grep -vP '[\x00-\x08\x0B\x0C\x0E-\x1F\x7F\x{202E}\x{200B}]' > pr.diff
echo "diff<<EOF" >> $GITHUB_OUTPUT
cat pr.diff >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Check and sanitize PR title
if: steps.actor.outputs.is_repo_dev == 'true'
id: sanitize_pr_title
env:
PR_TITLE_RAW: ${{ github.event.pull_request.title }}
run: |
# Sanitize PR title: max 72 characters, only printable characters
PR_TITLE=$(echo "$PR_TITLE_RAW" | tr -d '\n\r' | head -c 72 | sed 's/[^[:print:]]//g')
if [[ ${#PR_TITLE} -lt 5 ]]; then
echo "PR title is too short. Must be at least 5 characters." >&2
fi
echo "pr_title=$PR_TITLE" >> $GITHUB_OUTPUT
- name: AI PR Title Analysis
if: steps.actor.outputs.is_repo_dev == 'true'
id: ai-title-analysis
uses: actions/ai-inference@a7805884c80886efc241e94a5351df715968a0ad # v2.1.1
with:
model: openai/gpt-4o
system-prompt-file: ".github/config/system-prompt.txt"
prompt: |
Based on the following input data:
{
"diff": "${{ steps.get_diff.outputs.diff }}",
"pr_title": "${{ steps.sanitize_pr_title.outputs.pr_title }}"
}
Respond ONLY with valid JSON in the format:
{
"improved_rating": <0-10>,
"improved_ai_title_rating": <0-10>,
"improved_title": "<ai generated title>"
}
- name: Validate and set SCRIPT_OUTPUT
if: steps.actor.outputs.is_repo_dev == 'true'
run: |
cat <<EOF > ai_response.json
${{ steps.ai-title-analysis.outputs.response }}
EOF
# Validate JSON structure
jq -e '
(keys | sort) == ["improved_ai_title_rating", "improved_rating", "improved_title"] and
(.improved_rating | type == "number" and . >= 0 and . <= 10) and
(.improved_ai_title_rating | type == "number" and . >= 0 and . <= 10) and
(.improved_title | type == "string")
' ai_response.json
if [ $? -ne 0 ]; then
echo "Invalid AI response format" >&2
cat ai_response.json >&2
exit 1
fi
# Parse JSON fields
IMPROVED_RATING=$(jq -r '.improved_rating' ai_response.json)
IMPROVED_TITLE=$(jq -r '.improved_title' ai_response.json)
# Limit comment length to 1000 characters
COMMENT=$(cat <<EOF
## 🤖 AI PR Title Suggestion
**PR-Title Rating**: $IMPROVED_RATING/10
### ⬇️ Suggested Title (copy & paste):
\`\`\`
$IMPROVED_TITLE
\`\`\`
---
*Generated by GitHub Models AI*
EOF
)
echo "$COMMENT" > /tmp/ai-title-comment.md
# Log input and output to the GitHub Step Summary
echo "### 🤖 AI PR Title Analysis" >> $GITHUB_STEP_SUMMARY
echo "### Input PR Title" >> $GITHUB_STEP_SUMMARY
echo '```bash' >> $GITHUB_STEP_SUMMARY
echo "${{ steps.sanitize_pr_title.outputs.pr_title }}" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo '### AI Response (raw JSON)' >> $GITHUB_STEP_SUMMARY
echo '```json' >> $GITHUB_STEP_SUMMARY
cat ai_response.json >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
- name: Post comment on PR if needed
if: steps.actor.outputs.is_repo_dev == 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
continue-on-error: true
with:
github-token: ${{ steps.setup-bot.outputs.token }}
script: |
const fs = require('fs');
const body = fs.readFileSync('/tmp/ai-title-comment.md', 'utf8');
const { GITHUB_REPOSITORY } = process.env;
const [owner, repo] = GITHUB_REPOSITORY.split('/');
const issue_number = context.issue.number;
const ratingMatch = body.match(/\*\*PR-Title Rating\*\*: (\d+)\/10/);
const rating = ratingMatch ? parseInt(ratingMatch[1], 10) : null;
const expectedActor = "${{ steps.setup-bot.outputs.app-slug }}[bot]";
const comments = await github.rest.issues.listComments({ owner, repo, issue_number });
const existing = comments.data.find(c =>
c.user?.login === expectedActor &&
c.body.includes("## 🤖 AI PR Title Suggestion")
);
if (rating === null) {
console.log("No rating found in AI response skipping.");
return;
}
if (rating <= 5) {
if (existing) {
await github.rest.issues.updateComment({
owner, repo,
comment_id: existing.id,
body
});
console.log("Updated existing suggestion comment.");
} else {
await github.rest.issues.createComment({
owner, repo, issue_number,
body
});
console.log("Created new suggestion comment.");
}
} else {
const praise = `## 🤖 AI PR Title Suggestion\n\nGreat job! The current PR title is clear and well-structured.\n\n✅ No suggestions needed.\n\n---\n*Generated by GitHub Models AI*`;
if (existing) {
await github.rest.issues.updateComment({
owner, repo,
comment_id: existing.id,
body: praise
});
console.log("Replaced suggestion with praise.");
} else {
console.log("Rating > 5 and no existing comment skipping comment.");
}
}
- name: is not repo dev
if: steps.actor.outputs.is_repo_dev != 'true'
run: |
exit 0 # Skip the AI title review for non-repo developers
- name: Clean up
if: always()
run: |
rm -f pr.diff ai_response.json /tmp/ai-title-comment.md
echo "Cleaned up temporary files."
continue-on-error: true # Ensure cleanup runs even if previous steps fail
+4 -3
View File
@@ -26,7 +26,7 @@ jobs:
jar_sha256: ${{ steps.hashes.outputs.jar_sha256 }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
@@ -66,11 +66,12 @@ jobs:
echo "jar_sha256=$JAR_SHA" >> "$GITHUB_OUTPUT"
publish-aur:
environment: package-publish
needs: get-release-info
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
@@ -106,7 +107,7 @@ jobs:
- name: Publish stirling-pdf-desktop to AUR
if: ${{ github.event_name == 'release' || inputs.dry_run == false }}
uses: KSXGitHub/github-actions-deploy-aur@da03e160361ce01bf087e790b6ffd196d7dccff7 # v4.1.3
uses: KSXGitHub/github-actions-deploy-aur@084b0d9b15415bf9cdb65d44dad1efe37a354050 # v4.2.0
with:
pkgname: stirling-pdf-desktop
pkgbuild: .github/aur/stirling-pdf-desktop/PKGBUILD
+5 -10
View File
@@ -13,26 +13,21 @@ jobs:
labeler:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read # checkout + labeler fetching its config from the repo
pull-requests: write # read changed files, apply labels to the PR
issues: write # labels are applied through the issues API
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup GitHub App Bot
id: setup-bot
uses: ./.github/actions/setup-bot
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: srvaroa/labeler@bf262763a8a8e191f5847873aecc0f29df84f957 # v1.14.0
with:
config_path: .github/labeler-config-srvaroa.yml
use_local_config: false
fail_on_error: true
env:
GITHUB_TOKEN: "${{ steps.setup-bot.outputs.token }}"
GITHUB_TOKEN: "${{ github.token }}"
+15 -12
View File
@@ -20,6 +20,9 @@ permissions:
jobs:
build:
environment:
name: ci-unsigned
deployment: false
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -28,29 +31,26 @@ jobs:
flavor: [core, proprietary, saas]
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Restore cache Gradle User Home
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-${{ matrix.jdk-version }}-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK ${{ matrix.jdk-version }}
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: ${{ matrix.jdk-version }}
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-${{ matrix.jdk-version }}-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-${{ matrix.jdk-version }}-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
- name: Check Java formatting (Spotless)
@@ -153,6 +153,9 @@ jobs:
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }}
STIRLING_FLAVOR: ${{ matrix.flavor }}
# Configure the Gradle daemon explicitly; GRADLE_OPTS alone only
# configures the Gradle client JVM.
GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4g -XX:+UseG1GC"
- name: Check Test Reports Exist
if: always()
+38 -11
View File
@@ -15,6 +15,11 @@ name: Enterprise E2E (Playwright)
on:
workflow_call:
inputs:
use_shared_cache:
required: false
type: boolean
default: false
push:
branches: ["main"]
schedule:
@@ -37,6 +42,9 @@ jobs:
uses: ./.github/workflows/_runner-pick.yml
playwright-e2e-enterprise:
environment:
name: ci-unsigned
deployment: false
needs: pick
# Skip on fork PRs / untrusted authors: they have no PREMIUM_KEY_ENTERPRISE,
# so the suite can't boot premium and would fail. See the header comment.
@@ -50,26 +58,36 @@ jobs:
SYSTEM_ENABLEANALYTICS: "false"
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
- name: Restore cache Gradle User Home
if: inputs.use_shared_cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Restore cache Gradle
if: inputs.use_shared_cache == false
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
key: gradle-playwright-e2e-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
@@ -306,9 +324,18 @@ jobs:
path: frontend/playwright-report/
retention-days: 7
- name: Cleanup temporary files
if: always()
run: |
rm -f /tmp/helpers.sh /tmp/backend.log /tmp/backend.pid
continue-on-error: true
# Multi-node regression: builds + seeds the clustered stack (testing/compose/docker-compose-multinode.yml)
# and runs behave features/multinode. Licence-gated, so it runs after the Playwright job (not in parallel).
multinode-e2e:
environment:
name: ci-unsigned
deployment: false
needs: [pick, playwright-e2e-enterprise]
# Nightly cron + manual dispatch only (heavy build), fork-gated for the licence secret.
if: >-
@@ -324,7 +351,7 @@ jobs:
MN_COMPOSE: docker-compose-multinode.yml
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout repository
+38 -42
View File
@@ -37,6 +37,7 @@ jobs:
timeout-minutes: 3
outputs:
build: ${{ steps.changes.outputs.build }}
backend: ${{ steps.changes.outputs.backend }}
project: ${{ steps.changes.outputs.project }}
openapi: ${{ steps.changes.outputs.openapi }}
frontend: ${{ steps.changes.outputs.frontend }}
@@ -48,54 +49,25 @@ jobs:
proprietary: ${{ steps.changes.outputs.proprietary }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Check for file changes
uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
id: changes
with:
filters: .github/config/.files.yaml
gradle-cache-prime:
name: Prime shared Gradle cache
needs: [files-changed]
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Resolve backend dependencies
run: ./gradlew :stirling-pdf:classes -PnoSpotless --no-daemon
env:
STIRLING_FLAVOR: saas
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }}
uses: ./.github/workflows/gradle-cache-prime.yml
secrets: inherit
build:
if: needs.files-changed.outputs.backend == 'true'
needs: [files-changed, gradle-cache-prime]
permissions:
actions: read
@@ -134,9 +106,10 @@ jobs:
uses: ./.github/workflows/frontend-validation.yml
secrets: inherit
# Advisory: deliberately NOT in all-checks-passed. It reports on the stories a
# branch touches so a regression is visible in review, but a browser scan is
# too new here to block merges on. Promote it once its pass/fail proves stable.
# Required (in all-checks-passed). Scans the stories a branch touches in both
# light and dark; an axe violation in either theme blocks the merge. The
# whole-suite sweep (nightly.yml) still covers stories a change affects without
# touching them directly.
frontend-a11y:
if: needs.files-changed.outputs.frontend == 'true'
needs: [files-changed]
@@ -168,10 +141,12 @@ jobs:
contents: read
uses: ./.github/workflows/build-enterprise.yml
secrets: inherit
with:
use_shared_cache: true
check-licence:
if: needs.files-changed.outputs.build == 'true'
needs: [files-changed, build, gradle-cache-prime]
needs: [files-changed, gradle-cache-prime]
permissions:
contents: read
uses: ./.github/workflows/check-licence.yml
@@ -190,8 +165,22 @@ jobs:
docker-base-changed: ${{ needs.files-changed.outputs.docker-base }}
test-build-docker-images:
if: github.event_name == 'pull_request' && needs.files-changed.outputs.project == 'true'
needs: [files-changed, build, check-generateOpenApiDocs, check-licence, gradle-cache-prime]
if: |
always() &&
github.event_name == 'pull_request' &&
needs.files-changed.outputs.project == 'true' &&
contains(fromJSON('["success", "skipped"]'), needs.gradle-cache-prime.result) &&
contains(fromJSON('["success", "skipped"]'), needs.build.result) &&
contains(fromJSON('["success", "skipped"]'), needs.check-generateOpenApiDocs.result) &&
contains(fromJSON('["success", "skipped"]'), needs.check-licence.result)
needs:
[
files-changed,
build,
check-generateOpenApiDocs,
check-licence,
gradle-cache-prime,
]
permissions:
contents: read
packages: read
@@ -203,7 +192,7 @@ jobs:
tauri-build:
if: needs.files-changed.outputs.tauri == 'true'
needs: [files-changed]
needs: [files-changed, gradle-cache-prime]
permissions:
contents: read
pull-requests: write
@@ -217,6 +206,7 @@ jobs:
with:
platform: windows-macos
sign: true
use_shared_cache: true
ai-engine:
if: needs.files-changed.outputs.engine == 'true'
@@ -240,6 +230,8 @@ jobs:
pull-requests: write
uses: ./.github/workflows/check-generated-models.yml
secrets: inherit
with:
use_shared_cache: true
pre-commit:
needs: [files-changed]
@@ -286,10 +278,12 @@ jobs:
if: always()
needs:
- files-changed
- gradle-cache-prime
- build
- db-migration-test
- check-generateOpenApiDocs
- frontend-validation
- frontend-a11y
- playwright-e2e
- playwright-e2e-live
- playwright-e2e-enterprise
@@ -304,7 +298,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
@@ -312,10 +306,12 @@ jobs:
env:
RESULTS: |
files-changed=${{ needs.files-changed.result }}
gradle-cache-prime=${{ needs.gradle-cache-prime.result }}
build=${{ needs.build.result }}
db-migration-test=${{ needs.db-migration-test.result }}
check-generateOpenApiDocs=${{ needs.check-generateOpenApiDocs.result }}
frontend-validation=${{ needs.frontend-validation.result }}
frontend-a11y=${{ needs.frontend-a11y.result }}
playwright-e2e=${{ needs.playwright-e2e.result }}
playwright-e2e-live=${{ needs.playwright-e2e-live.result }}
playwright-e2e-enterprise=${{ needs.playwright-e2e-enterprise.result }}
+22 -10
View File
@@ -9,6 +9,11 @@ name: Check generated models
# post-merge safety net.
on:
workflow_call:
inputs:
use_shared_cache:
required: false
type: boolean
default: false
push:
branches: [main]
@@ -23,7 +28,7 @@ jobs:
pull-requests: write
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
@@ -39,22 +44,29 @@ jobs:
engine/uv.lock
cache-suffix: generated-models
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
- name: Restore cache Gradle User Home
if: inputs.use_shared_cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
java-version: "25"
distribution: "temurin"
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Cache Gradle User Home
- name: Restore cache Gradle
if: inputs.use_shared_cache == false
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
key: gradle-generated-models-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Set up Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+12 -12
View File
@@ -10,33 +10,33 @@ permissions:
jobs:
check-licence:
environment:
name: ci-unsigned
deployment: false
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Restore cache Gradle User Home
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
- name: Check licenses for compatibility
+12 -12
View File
@@ -11,33 +11,33 @@ permissions:
jobs:
check-generate-openapi-docs:
environment:
name: ci-unsigned
deployment: false
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Restore cache Gradle User Home
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
- name: Generate OpenAPI documentation
+33 -32
View File
@@ -23,29 +23,23 @@ jobs:
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
permissions:
contents: read # Checkout, and read translation files via the contents API
issues: write # Allow posting comments on issues/PRs
pull-requests: write # Allow writing to pull requests
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout main branch first
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup GitHub App Bot
id: setup-bot
uses: ./.github/actions/setup-bot
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Get PR data
id: get-pr-data
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
const prNumber = context.payload.pull_request.number;
const repoOwner = context.payload.repository.owner.login;
@@ -66,17 +60,18 @@ jobs:
- name: Fetch PR changed files
id: fetch-pr-changes
env:
GH_TOKEN: ${{ steps.setup-bot.outputs.token }}
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ steps.get-pr-data.outputs.pr_number }}
run: |
echo "Fetching PR changed files..."
echo "Getting list of changed files from PR..."
# Check if PR number exists
if [ -z "${{ steps.get-pr-data.outputs.pr_number }}" ]; then
if [ -z "${PR_NUMBER}" ]; then
echo "Error: PR number is empty"
exit 1
fi
# Get changed files and filter for TOML translation files
gh pr view ${{ steps.get-pr-data.outputs.pr_number }} --json files -q ".files[].path" | grep -E '^frontend/editor/public/locales/[a-zA-Z-]+/translation\.toml$' > changed_files.txt || echo "No matching TOML files found in PR"
gh pr view "${PR_NUMBER}" --json files -q ".files[].path" | grep -E '^frontend/editor/public/locales/[a-zA-Z-]+/translation\.toml$' > changed_files.txt || echo "No matching TOML files found in PR"
# Check if any files were found
if [ ! -s changed_files.txt ]; then
echo "No TOML translation files changed in this PR"
@@ -88,32 +83,36 @@ jobs:
- name: Determine reference file
id: determine-file
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
# Untrusted, fork-controlled values are passed via env, never interpolated into the script
PR_NUMBER: ${{ steps.get-pr-data.outputs.pr_number }}
REPO_OWNER: ${{ steps.get-pr-data.outputs.repo_owner }}
REPO_NAME: ${{ steps.get-pr-data.outputs.repo_name }}
PR_REPO_OWNER: ${{ github.event.pull_request.head.repo.owner.login }}
PR_REPO_NAME: ${{ github.event.pull_request.head.repo.name }}
PR_BRANCH: ${{ steps.get-pr-data.outputs.branch }}
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
const fs = require("fs");
const path = require("path");
const prNumber = ${{ steps.get-pr-data.outputs.pr_number }};
const repoOwner = "${{ steps.get-pr-data.outputs.repo_owner }}";
const repoName = "${{ steps.get-pr-data.outputs.repo_name }}";
const prRepoOwner = "${{ github.event.pull_request.head.repo.owner.login }}";
const prRepoName = "${{ github.event.pull_request.head.repo.name }}";
const branch = "${{ steps.get-pr-data.outputs.branch }}";
console.log(`Determining reference file for PR #${prNumber}`);
// Validate inputs
// Validate inputs before any use
const validateInput = (input, regex, name) => {
if (!regex.test(input)) {
if (typeof input !== "string" || !regex.test(input)) {
throw new Error(`Invalid ${name}: ${input}`);
}
return input;
};
validateInput(repoOwner, /^[a-zA-Z0-9_-]+$/, "repository owner");
validateInput(repoName, /^[a-zA-Z0-9._-]+$/, "repository name");
validateInput(branch, /^[a-zA-Z0-9._/-]+$/, "branch name");
const repoOwner = validateInput(process.env.REPO_OWNER, /^[a-zA-Z0-9_-]+$/, "repository owner");
const repoName = validateInput(process.env.REPO_NAME, /^[a-zA-Z0-9._-]+$/, "repository name");
const prRepoOwner = validateInput(process.env.PR_REPO_OWNER, /^[a-zA-Z0-9_-]+$/, "PR repository owner");
const prRepoName = validateInput(process.env.PR_REPO_NAME, /^[a-zA-Z0-9._-]+$/, "PR repository name");
const branch = validateInput(process.env.PR_BRANCH, /^[a-zA-Z0-9._/-]+$/, "branch name");
const prNumber = Number(validateInput(process.env.PR_NUMBER, /^[0-9]+$/, "PR number"));
console.log(`Determining reference file for PR #${prNumber}`);
// Get the list of changed files in the PR
const { data: files } = await github.rest.pulls.listFiles({
@@ -126,7 +125,7 @@ jobs:
const changedFiles = files
.filter(file =>
file.status !== "removed" &&
/^frontend\/public\/locales\/[a-zA-Z-]+\/translation\.toml$/.test(file.filename)
/^frontend\/editor\/public\/locales\/[a-zA-Z-]+\/translation\.toml$/.test(file.filename)
)
.map(file => file.filename);
@@ -209,10 +208,12 @@ jobs:
- name: Run Python script to check files
id: run-check
env:
PR_ACTOR: ${{ github.event.pull_request.user.login }}
run: |
echo "Running Python script to check TOML files..."
uv run --project engine --locked --group tools python .github/scripts/check_language_toml.py \
--actor ${{ github.event.pull_request.user.login }} \
--actor "${PR_ACTOR}" \
--reference-file "${REFERENCE_FILE}" \
--branch "pr-branch" \
--files "${FILES_LIST[@]}" > result.txt
@@ -245,7 +246,7 @@ jobs:
if: env.SCRIPT_OUTPUT != ''
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
const { GITHUB_REPOSITORY, SCRIPT_OUTPUT } = process.env;
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
@@ -261,7 +262,7 @@ jobs:
const comment = comments.data.find(c => c.body.includes("## 🌐 TOML Translation Verification Summary"));
// Only update or create comments by the action user
const expectedActor = "${{ steps.setup-bot.outputs.app-slug }}[bot]";
const expectedActor = "github-actions[bot]";
if (comment && comment.user.login === expectedActor) {
// Update existing comment
+9 -12
View File
@@ -34,29 +34,26 @@ jobs:
timeout-minutes: 15
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Restore cache Gradle User Home
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
+17 -12
View File
@@ -13,34 +13,34 @@ permissions:
jobs:
migration-test:
environment:
name: ci-unsigned
deployment: false
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Restore cache Gradle User Home
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: 25
distribution: temurin
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
# Keep the normal formatting path here so this smoke test exercises the
# same Gradle configuration as the backend build.
- name: Build Stirling-PDF JAR
@@ -81,3 +81,8 @@ jobs:
path: /tmp/stirling-migration-failed-*/app.log
retention-days: 7
if-no-files-found: warn
- name: Cleanup temporary files
if: always()
run: rm -rf /tmp/stirling-migration-failed-*
continue-on-error: true
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
-189
View File
@@ -1,189 +0,0 @@
name: Auto V2 Deploy on Push
on:
push:
branches:
- V2
- deploy-on-v2-commit
permissions:
contents: read
jobs:
deploy-v2-on-push:
runs-on: ubuntu-latest
concurrency:
group: deploy-v2-push-V2
cancel-in-progress: true
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Get commit hashes for frontend and backend
id: commit-hashes
run: |
# Get last commit that touched the frontend folder, docker/frontend, or docker/compose
FRONTEND_HASH=$(git log -1 --format="%H" -- frontend/ docker/frontend/ docker/compose/ 2>/dev/null || echo "")
if [ -z "$FRONTEND_HASH" ]; then
FRONTEND_HASH="no-frontend-changes"
fi
# Get last commit that touched backend code, docker/backend, or docker/compose
BACKEND_HASH=$(git log -1 --format="%H" -- app/ docker/backend/ docker/compose/ 2>/dev/null || echo "")
if [ -z "$BACKEND_HASH" ]; then
BACKEND_HASH="no-backend-changes"
fi
echo "Frontend hash: $FRONTEND_HASH"
echo "Backend hash: $BACKEND_HASH"
echo "frontend_hash=$FRONTEND_HASH" >> $GITHUB_OUTPUT
echo "backend_hash=$BACKEND_HASH" >> $GITHUB_OUTPUT
# Short hashes for tags
if [ "$FRONTEND_HASH" = "no-frontend-changes" ]; then
echo "frontend_short=no-frontend" >> $GITHUB_OUTPUT
else
echo "frontend_short=${FRONTEND_HASH:0:8}" >> $GITHUB_OUTPUT
fi
if [ "$BACKEND_HASH" = "no-backend-changes" ]; then
echo "backend_short=no-backend" >> $GITHUB_OUTPUT
else
echo "backend_short=${BACKEND_HASH:0:8}" >> $GITHUB_OUTPUT
fi
- name: Check if frontend image exists
id: check-frontend
run: |
if docker manifest inspect ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }} >/dev/null 2>&1; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "Frontend image already exists, skipping build"
else
echo "exists=false" >> $GITHUB_OUTPUT
echo "Frontend image needs to be built"
fi
- name: Check if backend image exists
id: check-backend
run: |
if docker manifest inspect ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }} >/dev/null 2>&1; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "Backend image already exists, skipping build"
else
echo "exists=false" >> $GITHUB_OUTPUT
echo "Backend image needs to be built"
fi
- name: Login to Docker Hub
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
- name: Build and push frontend image
if: steps.check-frontend.outputs.exists == 'false'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
file: ./docker/frontend/Dockerfile
push: true
cache-from: type=gha,scope=stirling-v2-frontend
cache-to: type=gha,mode=max,scope=stirling-v2-frontend
tags: |
${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }}
${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-latest
build-args: VERSION_TAG=v2-alpha
platforms: linux/amd64
- name: Build and push backend image
if: steps.check-backend.outputs.exists == 'false'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
file: ./docker/backend/Dockerfile
push: true
cache-from: type=gha,scope=stirling-v2-backend
cache-to: type=gha,mode=max,scope=stirling-v2-backend
tags: |
${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }}
${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-latest
build-args: VERSION_TAG=v2-alpha
platforms: linux/amd64
- name: Set up SSH
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.NEW_VPS_SSH_KEY }}" > ../private.key
chmod 600 ../private.key
- name: Deploy to VPS on port 3000
run: |
export UNIQUE_NAME=docker-compose-v2-$GITHUB_RUN_ID.yml
cat > $UNIQUE_NAME << EOF
version: '3.3'
services:
backend:
container_name: stirling-v2-backend
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }}
ports:
- "13000:8080"
volumes:
- /stirling/V2/data:/usr/share/tessdata:rw
- /stirling/V2/config:/configs:rw
- /stirling/V2/logs:/logs:rw
environment:
DISABLE_ADDITIONAL_FEATURES: "true"
SECURITY_ENABLELOGIN: "false"
SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: "Stirling-PDF V2"
UI_HOMEDESCRIPTION: "V2 Frontend/Backend Split"
UI_APPNAMENAVBAR: "V2 Deployment"
SYSTEM_MAXFILESIZE: "100"
METRICS_ENABLED: "true"
SYSTEM_GOOGLEVISIBILITY: "false"
SWAGGER_SERVER_URL: "https://demo.stirlingpdf.cloud"
baseUrl: "https://demo.stirlingpdf.cloud"
restart: on-failure:5
frontend:
container_name: stirling-v2-frontend
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }}
ports:
- "3000:80"
environment:
VITE_API_BASE_URL: "http://${{ secrets.NEW_VPS_HOST }}:13000"
depends_on:
- backend
restart: on-failure:5
EOF
# Copy to remote with unique name
scp -i ../private.key -o StrictHostKeyChecking=no $UNIQUE_NAME ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }}:/tmp/$UNIQUE_NAME
# SSH and rename/move atomically to avoid interference
ssh -i ../private.key -o StrictHostKeyChecking=no ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }} << ENDSSH
mkdir -p /stirling/V2/{data,config,logs}
mv /tmp/$UNIQUE_NAME /stirling/V2/docker-compose.yml
cd /stirling/V2
docker-compose down || true
docker-compose pull
docker-compose up -d
docker system prune -af --volumes || true
docker image prune -af --filter "until=336h" --filter "label!=keep=true" || true
ENDSSH
- name: Cleanup temporary files
if: always()
run: |
rm -f ../private.key
+13 -13
View File
@@ -17,6 +17,9 @@ permissions:
jobs:
docker-compose-tests:
environment:
name: ci-unsigned
deployment: false
runs-on: ubuntu-latest
permissions:
actions: write
@@ -25,30 +28,27 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Restore cache Gradle User Home
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
# When the PR changes the base image, test.sh builds it locally
# (stirling-pdf-base:local) into the daemon image store. A buildx
# container builder can't see that store, so skip it here and let
@@ -66,7 +66,7 @@ jobs:
- name: Install Docker Compose
run: |
sudo curl -SL "https://github.com/docker/compose/releases/download/v2.39.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo curl -SL "https://github.com/docker/compose/releases/download/v5.4.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
- name: Install uv
+14 -29
View File
@@ -11,48 +11,33 @@ permissions:
jobs:
playwright-e2e-live:
environment:
name: ci-unsigned
deployment: false
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Restore cache Gradle User Home
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
# Gradle does not retry 429s, and a cold cache resolving the buildscript
# classpath is exactly where Maven Central rate-limits us. Retry it here,
# where a failure is cheap, instead of inside the backgrounded bootRun.
- name: Prime Gradle dependencies
env:
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }}
run: |
for attempt in 1 2 3; do
if ./gradlew --quiet -PnoSpotless :stirling-pdf:classes; then
exit 0
fi
echo "::warning::Gradle dependency resolution failed (attempt $attempt of 3)"
sleep $((attempt * 30))
done
echo "::error::Gradle could not resolve dependencies after 3 attempts"
exit 1
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
+16 -3
View File
@@ -14,6 +14,11 @@ jobs:
playwright-e2e:
name: playwright-e2e (${{ matrix.browser }})
runs-on: ubuntu-latest
# The image already contains the Playwright browsers and all Linux
# dependencies. This keeps the matrix for per-browser reporting while
# avoiding three concurrent `playwright install --with-deps` runs.
container:
image: mcr.microsoft.com/playwright:v1.58.2-noble@sha256:6446946a1d9fd62d9ae501312a2d76a43ee688542b21622056a372959b65d63d
strategy:
# One browser breaking must not mask a failure in another - report all.
fail-fast: false
@@ -27,7 +32,7 @@ jobs:
project: stubbed-webkit
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout repository
@@ -40,15 +45,23 @@ jobs:
cache-dependency-path: frontend/package-lock.json
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
- name: Install Playwright (${{ matrix.browser }})
run: task e2e:install -- ${{ matrix.browser }}
- name: Build frontend (production bundle for vite preview)
env:
VITE_BUILD_FOR_PREVIEW: "1"
run: task frontend:build
- name: Run stubbed E2E tests (${{ matrix.browser }})
env:
# The official Playwright image expects its browser runtime under
# the root home directory. Keep this scoped to Playwright and use a
# neutral Docker config path so Docker does not read /root/.docker.
HOME: /root
DOCKER_CONFIG: /tmp/playwright-docker-config
PLAYWRIGHT_JSON_OUTPUT_FILE: ${{ github.workspace }}/frontend/playwright-report/results.json
NPM_CONFIG_PREFER_OFFLINE: "true"
NPM_CONFIG_FETCH_RETRIES: "5"
NPM_CONFIG_FETCH_RETRY_FACTOR: "2"
NPM_CONFIG_FETCH_RETRY_MINTIMEOUT: "1000"
NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT: "120000"
run: task e2e:stubbed-project PROJECT=${{ matrix.project }} -- --workers=3
- name: Flag flaky tests
# Runs regardless of the test outcome: a flaky test (passed on retry)
+3 -8
View File
@@ -3,17 +3,12 @@ name: Frontend a11y regression gate
# Reusable workflow called from build.yml when frontend sources change.
#
# Scans the stories this branch touches in real Chromium and runs axe against
# each. Existing violations are grandfathered in .storybook/a11y-baseline.json;
# the check fails on a NEW violation — a story breaking a rule it wasn't already
# breaking — or on a story that fails to render at all.
# each; the check fails on any axe violation, or on a story that fails to render
# at all.
#
# Only changed stories, because a full sweep is ~30 minutes: far too slow to sit
# in front of every merge. The whole suite is scanned nightly instead
# (nightly.yml), which catches anything a branch didn't touch.
#
# Advisory for now: this is not in build.yml's all-checks-passed list, so a
# failure reports without blocking. Promote it once a few weeks of runs show the
# pass/fail is stable.
on:
workflow_call:
@@ -26,7 +21,7 @@ jobs:
timeout-minutes: 25
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout repository
@@ -28,7 +28,7 @@ jobs:
licenses-backend: ${{ steps.changes.outputs.licenses-backend }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
@@ -36,12 +36,16 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Check for file changes
uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
id: changes
with:
filters: .github/config/.files.yaml
generate-frontend-license-report:
# ci-bot, not bot-identity: this job runs on PRs too, and bot-identity is main-only.
environment:
name: ci-bot
deployment: false
if: needs.files-changed.outputs.licenses-frontend == 'true'
name: Generate Frontend License Report
needs: files-changed
@@ -52,7 +56,7 @@ jobs:
repository-projects: write # Required for enabling automerge
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
@@ -316,6 +320,10 @@ jobs:
GH_TOKEN: ${{ steps.setup-bot.outputs.token }}
generate-backend-license-report:
# ci-bot, not bot-identity: this job runs on PRs too, and bot-identity is main-only.
environment:
name: ci-bot
deployment: false
if: needs.files-changed.outputs.licenses-backend == 'true'
needs: files-changed
name: Generate Backend License Report
@@ -326,7 +334,7 @@ jobs:
repository-projects: write # Required for enabling automerge
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
@@ -344,22 +352,19 @@ jobs:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
- name: Cache Gradle
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
key: gradle-license-report-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout repository
+66
View File
@@ -0,0 +1,66 @@
name: Prime Gradle Cache
on:
workflow_call:
push:
branches: ["main"]
permissions:
contents: read
jobs:
gradle-cache-prime:
environment:
name: ci-unsigned
deployment: false
name: Prime shared Gradle cache
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Calculate Gradle cache key
id: gradle-cache-key
shell: bash
run: |
echo "key=gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}" >> "$GITHUB_OUTPUT"
- name: Cache Gradle (lookup-only)
id: cache-gradle-restore
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ steps.gradle-cache-key.outputs.key }}
lookup-only: true
- name: Set up JDK 25
if: steps.cache-gradle-restore.outputs.cache-hit != 'true'
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Resolve backend dependencies
if: steps.cache-gradle-restore.outputs.cache-hit != 'true'
run: ./gradlew :stirling-pdf:classes --no-daemon
env:
STIRLING_FLAVOR: saas
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }}
- name: Save cache Gradle User Home
if: steps.cache-gradle-restore.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ steps.gradle-cache-key.outputs.key }}
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
issues: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
+74 -90
View File
@@ -38,6 +38,9 @@ permissions:
jobs:
determine-matrix:
environment:
name: ci-unsigned
deployment: false
if: ${{ vars.CI_PROFILE != 'lite' }}
runs-on: ubuntu-latest
outputs:
@@ -45,29 +48,26 @@ jobs:
version: ${{ steps.versionNumber.outputs.versionNumber }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Cache Gradle
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-tauri-releases-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
- name: Get version number
@@ -93,7 +93,7 @@ jobs:
ALL="$WINDOWS,$WINDOWS_ARM64,$MACOS,$LINUX"
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
case "${{ github.event.inputs.platform }}" in
case "${INPUT_PLATFORM}" in
"windows")
echo "matrix={\"include\":[$WINDOWS,$WINDOWS_ARM64]}" >> $GITHUB_OUTPUT
;;
@@ -115,7 +115,12 @@ jobs:
echo "matrix={\"include\":[$ALL]}" >> $GITHUB_OUTPUT
fi
env:
INPUT_PLATFORM: ${{ github.event.inputs.platform }}
build-jars:
environment:
name: ci-unsigned
deployment: false
needs: determine-matrix
runs-on: ubuntu-latest
strategy:
@@ -135,29 +140,26 @@ jobs:
file_suffix: "-server"
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Cache Gradle
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-tauri-releases-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Setup Node.js
if: matrix.variant.build_frontend == true
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
@@ -194,6 +196,7 @@ jobs:
retention-days: 1
build:
environment: release-signing
needs: determine-matrix
strategy:
fail-fast: false
@@ -201,11 +204,10 @@ jobs:
runs-on: ${{ matrix.platform }}
env:
SM_API_KEY: ${{ secrets.SM_API_KEY }}
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
RELEASE_GPG_PRIVATE_KEY: ${{ secrets.RELEASE_GPG_PRIVATE_KEY }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
allowed-endpoints: >
@@ -234,6 +236,14 @@ jobs:
toolchain: stable
targets: ${{ matrix.platform == 'macos-15' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
- name: Cache Gradle
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-tauri-releases-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
# x86_64 JDK is set up first so the aarch64 step below can leave its
# JAVA_HOME as the active one. The macOS universal JRE build needs
# jmods from both arches; the x64 path is captured into the env
@@ -257,17 +267,6 @@ jobs:
java-version: "25"
distribution: ${{ matrix.platform == 'windows-11-arm' && 'microsoft' || 'temurin' }}
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
@@ -292,7 +291,7 @@ jobs:
# DigiCert KeyLocker Setup (Cloud HSM)
- name: Setup DigiCert KeyLocker
id: digicert-setup
if: ${{ startsWith(matrix.platform, 'windows') && env.SM_API_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master') }}
if: ${{ startsWith(matrix.platform, 'windows') && env.SM_API_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/release') }}
uses: digicert/ssm-code-signing@1d820463733701cf1484c7eb5d7d24a15ca2c454 # v1.2.1
env:
SM_API_KEY: ${{ secrets.SM_API_KEY }}
@@ -302,22 +301,22 @@ jobs:
SM_HOST: ${{ secrets.SM_HOST }}
- name: Setup DigiCert KeyLocker Certificate
if: ${{ startsWith(matrix.platform, 'windows') && env.SM_API_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master') }}
if: ${{ startsWith(matrix.platform, 'windows') && env.SM_API_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/release') }}
shell: pwsh
run: |
Write-Host "Setting up DigiCert KeyLocker environment..."
# Decode client certificate
$certBytes = [Convert]::FromBase64String("${{ secrets.SM_CLIENT_CERT_FILE_B64 }}")
$certBytes = [Convert]::FromBase64String("$env:SM_CLIENT_CERT_FILE_B64")
$certPath = "D:\Certificate_pkcs12.p12"
[IO.File]::WriteAllBytes($certPath, $certBytes)
# Set environment variables
echo "SM_CLIENT_CERT_FILE=D:\Certificate_pkcs12.p12" >> $env:GITHUB_ENV
echo "SM_HOST=${{ secrets.SM_HOST }}" >> $env:GITHUB_ENV
echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> $env:GITHUB_ENV
echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> $env:GITHUB_ENV
echo "SM_KEYPAIR_ALIAS=${{ secrets.SM_KEYPAIR_ALIAS }}" >> $env:GITHUB_ENV
echo "SM_HOST=$env:SM_HOST" >> $env:GITHUB_ENV
echo "SM_API_KEY=$env:SM_API_KEY" >> $env:GITHUB_ENV
echo "SM_CLIENT_CERT_PASSWORD=$env:SM_CLIENT_CERT_PASSWORD" >> $env:GITHUB_ENV
echo "SM_KEYPAIR_ALIAS=$env:SM_KEYPAIR_ALIAS" >> $env:GITHUB_ENV
# Get PKCS11 config path from DigiCert action
$pkcs11Config = $env:PKCS11_CONFIG
@@ -335,40 +334,14 @@ jobs:
}
}
# Traditional PFX Certificate Import (fallback if KeyLocker not configured)
- name: Import Windows Code Signing Certificate
if: ${{ startsWith(matrix.platform, 'windows') && env.SM_API_KEY == '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master') }}
env:
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
shell: powershell
run: |
if ($env:WINDOWS_CERTIFICATE) {
Write-Host "Importing Windows Code Signing Certificate..."
# Decode base64 certificate and save to file
$certBytes = [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE)
$certPath = Join-Path $env:RUNNER_TEMP "certificate.pfx"
[IO.File]::WriteAllBytes($certPath, $certBytes)
# Import certificate to CurrentUser\My store
$cert = Import-PfxCertificate -FilePath $certPath -CertStoreLocation Cert:\CurrentUser\My -Password (ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -AsPlainText -Force)
# Extract and set thumbprint as environment variable
$thumbprint = $cert.Thumbprint
Write-Host "Certificate imported with thumbprint: $thumbprint"
echo "WINDOWS_CERTIFICATE_THUMBPRINT=$thumbprint" >> $env:GITHUB_ENV
# Clean up certificate file
Remove-Item $certPath
Write-Host "Windows certificate import completed."
} else {
Write-Host "⚠️ WINDOWS_CERTIFICATE secret not set - building unsigned binary"
}
SM_CLIENT_CERT_FILE_B64: ${{ secrets.SM_CLIENT_CERT_FILE_B64 }}
SM_HOST: ${{ secrets.SM_HOST }}
SM_API_KEY: ${{ secrets.SM_API_KEY }}
SM_CLIENT_CERT_PASSWORD: ${{ secrets.SM_CLIENT_CERT_PASSWORD }}
SM_KEYPAIR_ALIAS: ${{ secrets.SM_KEYPAIR_ALIAS }}
- name: Import Apple Developer Certificate
if: matrix.platform == 'macos-15' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master')
if: matrix.platform == 'macos-15' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/release')
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
@@ -389,7 +362,7 @@ jobs:
rm certificate.p12
- name: Verify Certificate
if: matrix.platform == 'macos-15' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master')
if: matrix.platform == 'macos-15' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/release')
run: |
echo "Verifying Apple Developer Certificate..."
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
@@ -405,7 +378,7 @@ jobs:
# Without this, signCommand failures are opaque (Tauri captures but drops
# smctl's stderr) - running these loudly surfaces auth/env/keypair issues.
- name: Preflight smctl
if: ${{ startsWith(matrix.platform, 'windows') && env.SM_API_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master') }}
if: ${{ startsWith(matrix.platform, 'windows') && env.SM_API_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/release') }}
shell: pwsh
env:
KEYPAIR_ALIAS: ${{ secrets.SM_KEYPAIR_ALIAS }}
@@ -436,7 +409,7 @@ jobs:
# smctl reads SM_HOST, SM_API_KEY, SM_CLIENT_CERT_FILE, SM_CLIENT_CERT_PASSWORD
# from env (set by prior DigiCert setup step). No --config-file needed.
- name: Configure Windows code signing
if: ${{ startsWith(matrix.platform, 'windows') && env.SM_API_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master') }}
if: ${{ startsWith(matrix.platform, 'windows') && env.SM_API_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/release') }}
shell: bash
env:
KEYPAIR_ALIAS: ${{ secrets.SM_KEYPAIR_ALIAS }}
@@ -457,7 +430,7 @@ jobs:
sed "s/${KEYPAIR_ALIAS}/***/g" ./frontend/editor/src-tauri/tauri.windows.conf.json
- name: Import release GPG signing key (Linux)
if: matrix.platform == 'ubuntu-22.04' && env.RELEASE_GPG_PRIVATE_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master')
if: matrix.platform == 'ubuntu-22.04' && env.RELEASE_GPG_PRIVATE_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/release')
run: |
echo "$RELEASE_GPG_PRIVATE_KEY" | gpg --batch --import
gpg --list-secret-keys --keyid-format=long
@@ -489,8 +462,8 @@ jobs:
# APPIMAGETOOL_SIGN_PASSPHRASE appimagetool uses this to unlock the GPG key non-interactively
# SIGN_KEY appimagetool picks the key matching this fingerprint
# Without SIGN=1, the other two are ignored and the AppImage is built unsigned even if a key is present.
# Mirror the Windows/macOS gate: only sign on a real release/dispatch+sign or V2-master, when secret is present.
SIGN: ${{ (env.RELEASE_GPG_PRIVATE_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master')) && '1' || '0' }}
# Mirror the Windows/macOS gate: only sign on a real release/dispatch+sign or the release branch, when secret is present.
SIGN: ${{ (env.RELEASE_GPG_PRIVATE_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/release')) && '1' || '0' }}
APPIMAGETOOL_SIGN_PASSPHRASE: ${{ secrets.RELEASE_GPG_PASSPHRASE }}
SIGN_KEY: ${{ vars.RELEASE_GPG_FINGERPRINT }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
@@ -516,7 +489,7 @@ jobs:
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
GPG_SIGN: ${{ (env.RELEASE_GPG_PRIVATE_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master')) && '1' || '0' }}
GPG_SIGN: ${{ (env.RELEASE_GPG_PRIVATE_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/release')) && '1' || '0' }}
SIGN_KEY: ${{ vars.RELEASE_GPG_FINGERPRINT }}
APPIMAGETOOL_SIGN_PASSPHRASE: ${{ secrets.RELEASE_GPG_PASSPHRASE }}
run: |
@@ -555,7 +528,7 @@ jobs:
echo "Stripped bundled libwayland from $(basename "$AI")"
- name: Clear release GPG key from runner keyring (Linux)
if: always() && matrix.platform == 'ubuntu-22.04' && env.RELEASE_GPG_PRIVATE_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master')
if: always() && matrix.platform == 'ubuntu-22.04' && env.RELEASE_GPG_PRIVATE_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/release')
env:
RELEASE_GPG_FINGERPRINT: ${{ vars.RELEASE_GPG_FINGERPRINT }}
run: |
@@ -570,7 +543,7 @@ jobs:
# artifact. Tauri signs a COPY when bundling into the MSI and leaves the raw
# cargo output unsigned, so checking it produces false negatives.
- name: Verify Windows Code Signature
if: ${{ startsWith(matrix.platform, 'windows') && env.SM_API_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master') }}
if: ${{ startsWith(matrix.platform, 'windows') && env.SM_API_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/release') }}
timeout-minutes: 15
shell: pwsh
run: |
@@ -724,6 +697,17 @@ jobs:
path: ./dist/*
retention-days: 1
- name: Cleanup temporary files
if: always()
shell: bash
run: |
rm -f certificate.p12
rm -rf "$RUNNER_TEMP/msi-verify"
if [ "${{ matrix.platform }}" = "macos-15" ]; then
security delete-keychain "$RUNNER_TEMP/app-signing.keychain-db" 2>/dev/null || true
fi
continue-on-error: true
collect-and-release:
needs: [determine-matrix, build, build-jars]
runs-on: ubuntu-latest
@@ -731,7 +715,7 @@ jobs:
contents: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
@@ -879,7 +863,7 @@ jobs:
PYEOF
- name: Upload merged artifacts for review
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-artifacts
path: ./artifacts/
@@ -902,11 +886,11 @@ jobs:
# workflow_dispatch path requires platform=='all' so a single-platform
# dispatch can't overwrite an existing release's full latest.json with a
# partial one (action-gh-release defaults overwrite_files:true).
# release / V2-master always build the full matrix so no extra guard needed.
# release event / release branch always build the full matrix so no extra guard needed.
# fail_on_unmatched_files makes a missing latest.json or installer fail loudly
# instead of silently shipping a broken auto-update.
- name: Upload binaries to Release
if: (github.event_name == 'workflow_dispatch' && github.event.inputs.test_mode != 'true' && github.event.inputs.platform == 'all') || github.event_name == 'release' || github.ref == 'refs/heads/V2-master'
if: (github.event_name == 'workflow_dispatch' && github.event.inputs.test_mode != 'true' && github.event.inputs.platform == 'all') || github.event_name == 'release' || github.ref == 'refs/heads/release'
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
tag_name: v${{ needs.determine-matrix.outputs.version }}
+94 -8
View File
@@ -4,6 +4,11 @@ on:
schedule:
- cron: "0 2 * * *" # 2 AM UTC every night
workflow_dispatch:
pull_request:
paths:
- .github/workflows/nightly.yml
- testing/cucumber/**
- docker/embedded/compose/test_cicd.yml
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -18,7 +23,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
@@ -59,13 +64,17 @@ jobs:
# the story itself — a shared component, a theme token — still surfaces within
# a day.
a11y-all-stories:
name: a11y (every story, light + dark)
name: a11y (every story)
strategy:
fail-fast: false
matrix:
theme: [light, dark]
runs-on: ubuntu-latest
# Two full sweeps (one per theme), each ~30 minutes of browser time.
timeout-minutes: 120
# One full sweep (~30 minutes of browser time).
timeout-minutes: 60
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
@@ -82,14 +91,14 @@ jobs:
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
- name: a11y gate (every story, light + dark)
run: task frontend:storybook:a11y
- name: a11y gate (every story, ${{ matrix.theme }})
run: task frontend:storybook:a11y:${{ matrix.theme }}
- name: Upload scan reports
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: a11y-scan-nightly-${{ github.run_id }}
name: a11y-scan-nightly-${{ matrix.theme }}-${{ github.run_id }}
path: frontend/.a11y-scan/
retention-days: 14
if-no-files-found: ignore
@@ -99,8 +108,13 @@ jobs:
# Builds all desktop platforms on a schedule so the Rust dependency cache is
# written on main, where PR and merge-queue tauri builds can restore it.
#
# The only job here still pinned to schedule/main: it primes a cache rather than
# testing anything, and Actions scopes a cache written on a PR branch to that PR
# alone, so a PR run costs three platform builds and produces nothing reusable.
warm-tauri-cache:
name: Warm Tauri Rust cache
if: github.event_name == 'schedule' || github.ref == 'refs/heads/main'
permissions:
contents: read
pull-requests: write
@@ -109,3 +123,75 @@ jobs:
platform: all
sign: false
secrets: inherit
# Runs the @nightly tag (conversion scenarios) plus a 10-shard concurrency run
# of every other feature.
cucumber-nightly:
environment:
name: ci-unsigned
deployment: false
name: Cucumber (nightly scenarios + full concurrency)
runs-on: ubuntu-latest
# Fork pull requests get no MAVEN_* secrets, so the image build cannot work.
if: >-
github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.full_name == github.repository
permissions:
contents: read
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
enable-cache: true
cache-dependency-glob: |
engine/pyproject.toml
engine/uv.lock
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
- name: Start the fat image with login and storage enabled
run: docker compose -f docker/embedded/compose/test_cicd.yml up -d --build
env:
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }}
- name: Wait for the server
# Throwaway key from test_cicd.yml; out of the header literal for gitleaks.
env:
TEST_API_KEY: "123456789"
run: |
curl --retry 90 --retry-delay 3 --retry-connrefused --retry-all-errors \
-sf -H "X-API-KEY: $TEST_API_KEY" http://localhost:8080/api/v1/info/status
# Heavy LibreOffice/Calibre/Ghostscript conversions, excluded from the PR run.
# Both tasks install the behave deps themselves, so there is no separate uv sync step.
- name: Run @nightly scenarios
run: task cucumber:nightly
# Genuinely different payloads contending on one backend.
- name: Sharded concurrency validation
run: task cucumber:parallel SHARDS=10
- name: Container logs on failure
if: failure()
run: docker compose -f docker/embedded/compose/test_cicd.yml logs --tail 400
- name: Tear down
if: always()
run: docker compose -f docker/embedded/compose/test_cicd.yml down -v
+3 -2
View File
@@ -28,7 +28,7 @@ jobs:
jar_sha256: ${{ steps.hashes.outputs.jar_sha256 }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
@@ -73,13 +73,14 @@ jobs:
echo "jar_sha256=$JAR_SHA" >> "$GITHUB_OUTPUT"
update-homebrew-and-scoop:
environment: package-publish
needs: get-release-info
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
+5 -12
View File
@@ -27,29 +27,22 @@ jobs:
name: Label conflicted PRs
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
pull-requests: read
contents: read # actions/checkout
issues: write # get/create the repo-level conflict label
pull-requests: write # pulls.get/list plus add/remove the label on PRs
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Check out the repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up stirling-bot token
id: setup-bot
uses: ./.github/actions/setup-bot
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Apply conflict label
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
const conflictLabel = process.env.CONFLICT_LABEL;
const owner = context.repo.owner;
+2 -2
View File
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
@@ -34,7 +34,7 @@ jobs:
cache-suffix: pre-commit
- name: Install Task
uses: go-task/setup-task@3be4020d41929789a01026e0e427a4321ce0ad44 # v2.0.0
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
- name: Run pre-commit checks
run: task pre-commit
+7 -2
View File
@@ -17,6 +17,9 @@ permissions:
jobs:
push-base:
# Own environment: docker-publish is branch-locked to release/main,
# which excludes the baseDockerImage/accessIssueFix branches this runs on.
environment: docker-base-publish
if: ${{ vars.CI_PROFILE != 'lite' && github.actor == 'Frooodle' }}
runs-on: ubuntu-24.04-8core
permissions:
@@ -32,9 +35,11 @@ jobs:
- name: Set version
id: version
env:
INPUT_VERSION: ${{ github.event.inputs.version }}
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
VERSION="${{ github.event.inputs.version }}"
VERSION="${INPUT_VERSION}"
elif [ "${{ github.ref_name }}" == "accessIssueFix" ]; then
VERSION="1.0.3"
else
@@ -43,7 +48,7 @@ jobs:
echo "version=${VERSION}" >> $GITHUB_OUTPUT
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
+24 -28
View File
@@ -20,10 +20,8 @@ on:
default: false
push:
branches:
- master
- release
- main
- V2-master
- testMain
# cancel in-progress jobs if a new job is triggered
# This is useful to avoid running multiple builds for the same branch if a new commit is pushed
@@ -42,6 +40,7 @@ permissions:
jobs:
push:
environment: docker-publish
if: ${{ vars.CI_PROFILE != 'lite' }}
runs-on: ubuntu-24.04-8core
permissions:
@@ -53,29 +52,26 @@ jobs:
RUN_UNOSERVER: ${{ github.event_name != 'workflow_dispatch' || inputs.build_unoserver }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Cache Gradle
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-push-docker-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
@@ -91,13 +87,13 @@ jobs:
MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }}
- name: Install cosign
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master'
if: github.ref == 'refs/heads/release'
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: "v2.4.1"
- name: Install cosign
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master'
if: github.ref == 'refs/heads/release'
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: "v2.4.1"
@@ -133,8 +129,8 @@ jobs:
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf
${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf
tags: |
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }},enable=${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master' }}
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master' }}
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }},enable=${{ github.ref == 'refs/heads/release' }}
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/release' }}
- name: Build and push Unified Dockerfile (latest variant)
id: build-push-latest
@@ -158,7 +154,7 @@ jobs:
sbom: true
- name: Sign regular images
if: env.RUN_MAIN_APP == 'true' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master') && steps.build-push-latest.outputs.digest != ''
if: env.RUN_MAIN_APP == 'true' && (github.ref == 'refs/heads/release') && steps.build-push-latest.outputs.digest != ''
env:
DIGEST: ${{ steps.build-push-latest.outputs.digest }}
TAGS: ${{ steps.meta.outputs.tags }}
@@ -182,8 +178,8 @@ jobs:
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf
${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf
tags: |
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-fat,enable=${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master' }}
type=raw,value=latest-fat,enable=${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master' }}
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-fat,enable=${{ github.ref == 'refs/heads/release' }}
type=raw,value=latest-fat,enable=${{ github.ref == 'refs/heads/release' }}
- name: Build and push Unified Dockerfile (fat variant)
id: build-push-fat
@@ -204,7 +200,7 @@ jobs:
sbom: true
- name: Sign fat images
if: env.RUN_MAIN_APP == 'true' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master') && steps.build-push-fat.outputs.digest != ''
if: env.RUN_MAIN_APP == 'true' && (github.ref == 'refs/heads/release') && steps.build-push-fat.outputs.digest != ''
env:
DIGEST: ${{ steps.build-push-fat.outputs.digest }}
TAGS: ${{ steps.meta-fat.outputs.tags }}
@@ -226,8 +222,8 @@ jobs:
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf
${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf
tags: |
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-ultra-lite,enable=${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master' }}
type=raw,value=latest-ultra-lite,enable=${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master' }}
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-ultra-lite,enable=${{ github.ref == 'refs/heads/release' }}
type=raw,value=latest-ultra-lite,enable=${{ github.ref == 'refs/heads/release' }}
- name: Build and push Unified Dockerfile (ultra-lite variant)
id: build-push-lite
@@ -248,7 +244,7 @@ jobs:
sbom: true
- name: Sign ultra-lite images
if: env.RUN_MAIN_APP == 'true' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master') && steps.build-push-lite.outputs.digest != ''
if: env.RUN_MAIN_APP == 'true' && (github.ref == 'refs/heads/release') && steps.build-push-lite.outputs.digest != ''
env:
DIGEST: ${{ steps.build-push-lite.outputs.digest }}
TAGS: ${{ steps.meta-lite.outputs.tags }}
@@ -260,7 +256,7 @@ jobs:
done
# Standalone unoserver image — versioned independently via
# docker/unoserver/VERSION. master/V2-master: publish <version>+latest
# docker/unoserver/VERSION. release: publish <version>+latest
# only when the version is new. main/testMain: republish :alpha only
# when the source hash differs from the published image's annotation.
- name: Read unoserver image version
@@ -319,7 +315,7 @@ jobs:
fi
case "$EFFECTIVE_REF" in
refs/heads/master|refs/heads/V2-master)
refs/heads/release)
if [ "${FORCE_REBUILD}" = "true" ]; then
echo "force_unoserver_rebuild=true — building stable regardless"
mode="stable"
+2 -1
View File
@@ -13,12 +13,13 @@ permissions:
jobs:
rollback:
environment: docker-publish
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
+2 -2
View File
@@ -35,7 +35,7 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
@@ -75,6 +75,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v3.29.5
uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
with:
sarif_file: results.sarif
+2 -2
View File
@@ -17,12 +17,12 @@ jobs:
pull-requests: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: 30 days stale issues
uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 30
+13 -13
View File
@@ -4,7 +4,7 @@ on:
workflow_dispatch:
push:
branches:
- master
- release
# cancel in-progress jobs if a new job is triggered
# This is useful to avoid running multiple builds for the same branch if a new commit is pushed
@@ -23,33 +23,33 @@ permissions:
jobs:
push:
# package-publish holds SWAGGERHUB_API_KEY. It requires reviewer approval and
# is limited to main / release / v* tags, so every push to release waits on one.
environment: package-publish
if: ${{ vars.CI_PROFILE != 'lite' }}
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Cache Gradle
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-swagger-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Generate Swagger documentation
run: ./gradlew :stirling-pdf:generateOpenApiDocs
+2 -1
View File
@@ -24,6 +24,7 @@ permissions:
jobs:
sync:
environment: bot-identity
name: Sync docs manifest
runs-on: ubuntu-latest
timeout-minutes: 10
@@ -32,7 +33,7 @@ jobs:
pull-requests: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
+3 -2
View File
@@ -33,10 +33,11 @@ permissions:
jobs:
sync-files:
environment: bot-identity
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
@@ -65,7 +66,7 @@ jobs:
uv sync --project engine --locked --group tools
- name: Install Task
uses: go-task/setup-task@3be4020d41929789a01026e0e427a4321ce0ad44 # v2.0.0
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
- name: Sync translation TOML files
run: |
+60 -54
View File
@@ -26,6 +26,10 @@ on:
required: false
type: boolean
default: false
use_shared_cache:
required: false
type: boolean
default: false
workflow_dispatch:
inputs:
platform:
@@ -57,13 +61,18 @@ permissions:
jobs:
determine-matrix:
# Only probes APPLE_CERTIFICATE for presence, so it stays on the unrestricted
# signing environment - release-signing would block every PR run.
environment:
name: ci-signing
deployment: false
if: ${{ vars.CI_PROFILE != 'lite' }}
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
@@ -103,6 +112,12 @@ jobs:
echo "matrix={\"include\":[$JOINED]}" >> $GITHUB_OUTPUT
build:
# Windows/GPG signing only runs on main (see the per-step gates below), so only
# that path needs the reviewer-gated release-signing environment. Everything else
# (PRs, merge queue, nightly) signs macOS only and uses ci-signing, which has no
# approval or branch restriction.
environment:
name: ${{ (inputs.sign && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))) && 'release-signing' || 'ci-signing' }}
needs: determine-matrix
strategy:
fail-fast: false
@@ -110,7 +125,6 @@ jobs:
runs-on: ${{ matrix.platform }}
env:
SM_API_KEY: ${{ secrets.SM_API_KEY }}
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
RELEASE_GPG_PRIVATE_KEY: ${{ secrets.RELEASE_GPG_PRIVATE_KEY }}
# Per-platform sign gate. macOS signs on any run with the cert available,
@@ -120,7 +134,7 @@ jobs:
SIGN_BUNDLE: ${{ inputs.sign && (matrix.platform == 'macos-15' && secrets.APPLE_CERTIFICATE != '' || github.ref == 'refs/heads/main') }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
@@ -150,7 +164,7 @@ jobs:
# only recompiles the app crate. Written on main; PRs and the merge queue
# restore from it.
- name: Cache Rust build
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
workspaces: frontend/editor/src-tauri
# Stable key shared across workflows so the nightly warmer.
@@ -160,6 +174,24 @@ jobs:
# Save the dependency cache even if a later step fails
cache-on-failure: true
- name: Restore cache Gradle User Home
if: inputs.use_shared_cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Restore cache Gradle
if: inputs.use_shared_cache == false
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-tauri-build-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up x86_64 JDK 25 (macOS universal JRE)
if: matrix.platform == 'macos-15'
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
@@ -179,17 +211,6 @@ jobs:
java-version: "25"
distribution: ${{ matrix.platform == 'windows-11-arm' && 'microsoft' || 'temurin' }}
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Setup Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
@@ -227,20 +248,26 @@ jobs:
- name: Setup DigiCert KeyLocker Certificate
if: ${{ inputs.sign && startsWith(matrix.platform, 'windows') && env.SM_API_KEY != '' && github.ref == 'refs/heads/main' }}
shell: pwsh
env:
SM_CLIENT_CERT_FILE_B64: ${{ secrets.SM_CLIENT_CERT_FILE_B64 }}
SM_HOST: ${{ secrets.SM_HOST }}
SM_API_KEY: ${{ secrets.SM_API_KEY }}
SM_CLIENT_CERT_PASSWORD: ${{ secrets.SM_CLIENT_CERT_PASSWORD }}
SM_KEYPAIR_ALIAS: ${{ secrets.SM_KEYPAIR_ALIAS }}
run: |
Write-Host "Setting up DigiCert KeyLocker environment..."
# Decode client certificate
$certBytes = [Convert]::FromBase64String("${{ secrets.SM_CLIENT_CERT_FILE_B64 }}")
$certBytes = [Convert]::FromBase64String("$env:SM_CLIENT_CERT_FILE_B64")
$certPath = "D:\Certificate_pkcs12.p12"
[IO.File]::WriteAllBytes($certPath, $certBytes)
# Set environment variables
echo "SM_CLIENT_CERT_FILE=D:\Certificate_pkcs12.p12" >> $env:GITHUB_ENV
echo "SM_HOST=${{ secrets.SM_HOST }}" >> $env:GITHUB_ENV
echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> $env:GITHUB_ENV
echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> $env:GITHUB_ENV
echo "SM_KEYPAIR_ALIAS=${{ secrets.SM_KEYPAIR_ALIAS }}" >> $env:GITHUB_ENV
echo "SM_HOST=$env:SM_HOST" >> $env:GITHUB_ENV
echo "SM_API_KEY=$env:SM_API_KEY" >> $env:GITHUB_ENV
echo "SM_CLIENT_CERT_PASSWORD=$env:SM_CLIENT_CERT_PASSWORD" >> $env:GITHUB_ENV
echo "SM_KEYPAIR_ALIAS=$env:SM_KEYPAIR_ALIAS" >> $env:GITHUB_ENV
# Get PKCS11 config path from DigiCert action
$pkcs11Config = $env:PKCS11_CONFIG
@@ -258,38 +285,6 @@ jobs:
}
}
# Traditional PFX Certificate Import (fallback if KeyLocker not configured)
- name: Import Windows Code Signing Certificate
if: ${{ inputs.sign && startsWith(matrix.platform, 'windows') && env.SM_API_KEY == '' && github.ref == 'refs/heads/main' }}
env:
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
shell: powershell
run: |
if ($env:WINDOWS_CERTIFICATE) {
Write-Host "Importing Windows Code Signing Certificate..."
# Decode base64 certificate and save to file
$certBytes = [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE)
$certPath = Join-Path $env:RUNNER_TEMP "certificate.pfx"
[IO.File]::WriteAllBytes($certPath, $certBytes)
# Import certificate to CurrentUser\My store
$cert = Import-PfxCertificate -FilePath $certPath -CertStoreLocation Cert:\CurrentUser\My -Password (ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -AsPlainText -Force)
# Extract and set thumbprint as environment variable
$thumbprint = $cert.Thumbprint
Write-Host "Certificate imported with thumbprint: $thumbprint"
echo "WINDOWS_CERTIFICATE_THUMBPRINT=$thumbprint" >> $env:GITHUB_ENV
# Clean up certificate file
Remove-Item $certPath
Write-Host "Windows certificate import completed."
} else {
Write-Host "⚠️ WINDOWS_CERTIFICATE secret not set - building unsigned binary"
}
- name: Import Apple Developer Certificate
if: env.SIGN_BUNDLE == 'true' && matrix.platform == 'macos-15'
env:
@@ -682,6 +677,17 @@ jobs:
fi
done
- name: Cleanup temporary files
if: always()
shell: bash
run: |
rm -f certificate.p12
rm -rf "$RUNNER_TEMP/msi-verify"
if [ "${{ matrix.platform }}" = "macos-15" ]; then
security delete-keychain "$RUNNER_TEMP/app-signing.keychain-db" 2>/dev/null || true
fi
continue-on-error: true
pr-comment:
needs: build
runs-on: ubuntu-latest
@@ -697,7 +703,7 @@ jobs:
pull-requests: write
steps:
- name: Harden the runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
@@ -789,7 +795,7 @@ jobs:
if: always()
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
+58 -32
View File
@@ -22,21 +22,45 @@ permissions:
contents: read
jobs:
# TODO: extract a pre-matrix `prepare` job that runs once and produces
# shared artifacts for the three matrix entries below to consume:
# 1. `task backend:build` — currently runs 3× in parallel with
# identical env (DISABLE_ADDITIONAL_FEATURES=true,
# STIRLING_PDF_DESKTOP_UI=false). Build once, upload the JAR as an
# artifact, matrix entries download.
# 2. The base-image `docker build` (gated on docker-base-changed) —
# currently runs 3× in parallel against the same Dockerfile and
# context. Build once, `docker save` to an artifact, matrix entries
# `docker load` before the embedded build.
# Saves ~2 full backend builds + 2 base-image builds per PR that touches
# docker. May also be reusable from backend-build.yml's jdk-25 +
# spring-security=true matrix entry if `task backend:build` and
# `task backend:build:ci` produce equivalent JARs (verify before wiring).
# A changed base image is shared by all three embedded-image builds. Build
# it once and transfer it as an artifact; the matrix jobs use the local
# Docker driver so the loaded image is visible to the build.
prepare-base-image:
if: github.event_name == 'pull_request' && inputs.docker-base-changed == 'true'
environment:
name: ci-unsigned
deployment: false
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Harden Runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Build base image locally
run: docker build --platform linux/amd64 -t stirling-pdf-base:pr-test -f docker/base/Dockerfile docker/base
- name: Export base image
run: docker save stirling-pdf-base:pr-test | gzip -1 > stirling-pdf-base-pr-test.tar.gz
- name: Upload base image
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: docker-base-pr-test
path: stirling-pdf-base-pr-test.tar.gz
retention-days: 1
if-no-files-found: error
test-build-docker-images:
if: always() && (needs.prepare-base-image.result == 'success' || needs.prepare-base-image.result == 'skipped')
needs: [prepare-base-image]
environment:
name: ci-unsigned
deployment: false
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -53,7 +77,7 @@ jobs:
cache-scope: stirling-pdf-fat
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
@@ -78,23 +102,30 @@ jobs:
docker system prune -af || true
echo "Disk space after cleanup:" && df -h
- name: Download prepared base image
if: github.event_name == 'pull_request' && inputs.docker-base-changed == 'true'
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: docker-base-pr-test
- name: Load prepared base image
if: github.event_name == 'pull_request' && inputs.docker-base-changed == 'true'
run: gzip -dc stirling-pdf-base-pr-test.tar.gz | docker load
- name: Restore cache Gradle User Home
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
- name: Build application
@@ -113,11 +144,6 @@ jobs:
id: buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Build base image locally (PR base change only)
if: github.event_name == 'pull_request' && inputs.docker-base-changed == 'true'
run: |
docker build -t stirling-pdf-base:pr-test -f docker/base/Dockerfile docker/base
- name: Set base image and platform for this build
id: build-params
# Pass workflow inputs through env vars rather than expanding `${{ }}`
@@ -191,7 +217,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
-213
View File
@@ -1,213 +0,0 @@
name: UI test with TestDriverAI
on:
push:
branches: ["master", "UITest", "testdriver"]
# cancel in-progress jobs if a new job is triggered
# This is useful to avoid running multiple builds for the same branch if a new commit is pushed
# or a pull request is updated.
# It helps to save resources and time by ensuring that only the latest commit is built and tested
# This is particularly useful for long-running jobs that may take a while to complete.
# The `group` is set to a combination of the workflow name, event name, and branch name.
# This ensures that jobs are grouped by the workflow and branch, allowing for cancellation of
# in-progress jobs when a new commit is pushed to the same branch or a new pull request is opened.
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref_name || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
deploy:
if: ${{ vars.CI_PROFILE != 'lite' }}
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Build with Gradle
run: ./gradlew build
env:
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }}
DISABLE_ADDITIONAL_FEATURES: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Get version number
id: versionNumber
run: |
VERSION=$(grep "^version =" build.gradle | awk -F'"' '{print $2}')
echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
- name: Login to Docker Hub
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
- name: Build and push test image
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
file: ./docker/embedded/Dockerfile
push: true
cache-from: type=gha,scope=stirling-pdf-latest
cache-to: type=gha,mode=max,scope=stirling-pdf-latest
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:test-${{ github.sha }}
build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
platforms: linux/amd64
- name: Set up SSH
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.NEW_VPS_SSH_KEY }}" > ../private.key
sudo chmod 600 ../private.key
- name: Deploy to VPS
run: |
cat > docker-compose.yml << EOF
version: '3.3'
services:
stirling-pdf:
container_name: stirling-pdf-test-${{ github.sha }}
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:test-${{ github.sha }}
ports:
- "1337:8080"
volumes:
- /stirling/test-${{ github.sha }}/data:/usr/share/tessdata:rw
- /stirling/test-${{ github.sha }}/config:/configs:rw
- /stirling/test-${{ github.sha }}/logs:/logs:rw
environment:
DISABLE_ADDITIONAL_FEATURES: "true"
SECURITY_ENABLELOGIN: "false"
SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: "Stirling-PDF Test"
UI_HOMEDESCRIPTION: "Test Deployment"
UI_APPNAMENAVBAR: "Test"
SYSTEM_MAXFILESIZE: "100"
METRICS_ENABLED: "true"
SYSTEM_GOOGLEVISIBILITY: "false"
SYSTEM_ENABLEANALYTICS: "false"
restart: on-failure:5
EOF
scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null docker-compose.yml ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }}:/tmp/docker-compose.yml
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }} << EOF
mkdir -p /stirling/test-${{ github.sha }}/{data,config,logs}
mv /tmp/docker-compose.yml /stirling/test-${{ github.sha }}/docker-compose.yml
cd /stirling/test-${{ github.sha }}
docker-compose pull
docker-compose up -d
EOF
files-changed:
if: always()
name: detect what files changed
runs-on: ubuntu-latest
timeout-minutes: 3
outputs:
frontend: ${{ steps.changes.outputs.frontend }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Check for file changes
uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
id: changes
with:
filters: ".github/config/.files.yaml"
test:
if: needs.files-changed.outputs.frontend == 'true'
needs: [deploy, files-changed]
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Run TestDriver.ai
uses: testdriverai/action@f0d0f45fdd684db628baa843fe9313f3ca3a8aa8 #1.1.3
with:
key: ${{secrets.TESTDRIVER_API_KEY}}
prerun: |
choco install go-task -y
task frontend:build
cd frontend
npm install dashcam-chrome --save
Start-Process "C:/Program Files/Google/Chrome/Application/chrome.exe" -ArgumentList "--start-maximized", "--load-extension=$(pwd)/node_modules/dashcam-chrome/build", "http://${{ secrets.NEW_VPS_HOST }}:1337"
Start-Sleep -Seconds 20
prompt: |
1. /run testing/testdriver/test.yml
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
FORCE_COLOR: "3"
cleanup:
needs: [deploy, test]
runs-on: ubuntu-latest
if: always()
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Set up SSH
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.NEW_VPS_SSH_KEY }}" > ../private.key
sudo chmod 600 ../private.key
- name: Cleanup deployment
if: always()
run: |
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }} << EOF
cd /stirling/test-${{ github.sha }}
docker-compose down
cd /stirling
rm -rf test-${{ github.sha }}
EOF
continue-on-error: true # Ensure cleanup runs even if previous steps fail
+112
View File
@@ -0,0 +1,112 @@
name: Update Gradle
on:
workflow_dispatch:
schedule:
- cron: "0 3 * * 1"
concurrency:
group: update-gradle
cancel-in-progress: true
jobs:
update-gradle:
name: Update Gradle and Docker images
permissions:
contents: write
pull-requests: write
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Harden runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Java
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: temurin
java-version: "25"
- name: Find latest Gradle release
id: gradle
shell: bash
run: |
set -euo pipefail
version=$(curl --fail --silent --show-error --retry 3 \
https://services.gradle.org/versions/current | jq -r '.version')
[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || {
echo "Could not determine a stable Gradle version: $version" >&2
exit 1
}
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Find matching Docker image digest
id: docker
env:
GRADLE_VERSION: ${{ steps.gradle.outputs.version }}
shell: bash
run: |
set -euo pipefail
tag="${GRADLE_VERSION}-jdk25"
digest=$(curl --fail --silent --show-error --retry 3 \
"https://hub.docker.com/v2/repositories/library/gradle/tags/${tag}" \
| jq -r '.digest // empty')
[[ "$digest" =~ ^sha256:[0-9a-f]{64}$ ]] || {
echo "Docker image gradle:${tag} was not found" >&2
exit 1
}
echo "tag=$tag" >> "$GITHUB_OUTPUT"
echo "digest=$digest" >> "$GITHUB_OUTPUT"
- name: Update Gradle wrapper
env:
GRADLE_VERSION: ${{ steps.gradle.outputs.version }}
run: ./gradlew wrapper --gradle-version "$GRADLE_VERSION" --distribution-type bin
- name: Update Gradle Docker images
env:
DOCKER_TAG: ${{ steps.docker.outputs.tag }}
DOCKER_DIGEST: ${{ steps.docker.outputs.digest }}
shell: bash
run: |
set -euo pipefail
find docker -type f -name 'Dockerfile*' -print0 |
xargs -0 sed -E -i \
"s#gradle:[^@[:space:]]+-jdk25(@sha256:[^[:space:]]+)?#gradle:${DOCKER_TAG}@${DOCKER_DIGEST}#g"
- name: Verify Gradle update
env:
EXPECTED_VERSION: ${{ steps.gradle.outputs.version }}
shell: bash
run: |
set -euo pipefail
actual=$(./gradlew --version | sed -n 's/^Gradle \([0-9.]*\)$/\1/p')
[[ "$actual" == "$EXPECTED_VERSION" ]] || {
echo "Wrapper resolved Gradle $actual, expected $EXPECTED_VERSION" >&2
exit 1
}
if git diff --quiet; then
echo "Gradle is already up to date."
exit 0
fi
git diff --check
- name: Create pull request
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: automation/update-gradle
delete-branch: true
commit-message: "chore: update Gradle"
title: "chore: update Gradle to ${{ steps.gradle.outputs.version }}"
body: |
Automated update of the Gradle wrapper and Gradle Docker build images.
Gradle version: `${{ steps.gradle.outputs.version }}`
Docker image: `gradle:${{ steps.docker.outputs.tag }}`
labels: dependencies
+12
View File
@@ -26,6 +26,7 @@ watchedFolders/
# also matches this frontend source component dir; keep the source tracked.
!frontend/editor/src/proprietary/components/watchedFolders/
clientWebUI/
policy-webhook-spool/
# Scratch dir used by local fixture-regeneration runs (see
# app/proprietary/src/test/resources/db-migration-fixtures/README.md).
# Holds downloaded JARs and disposable workdirs. Never committed.
@@ -38,6 +39,7 @@ exampleYmlFiles/stirling/
/testing/file_snapshots
/testing/cucumber/junit/
/testing/cucumber/report.html
/testing/cucumber/.parallel/
/testing/.failed_tests
/.test-state/
SwaggerDoc.json
@@ -63,13 +65,23 @@ app/core/src/main/resources/static/og_images/
app/core/src/main/resources/static/samples/
app/core/src/main/resources/static/manifest-classic.json
app/core/src/main/resources/static/og-metadata.json
app/core/src/main/resources/static/og-metadata.saas.json
app/core/src/main/resources/static/sw-folder-retry.js
app/core/src/main/resources/static/robots.txt
app/core/src/main/resources/static/android-chrome-*.png
app/core/src/main/resources/static/mstile-*.png
app/core/src/main/resources/static/favicon.png
app/core/src/main/resources/static/safari-pinned-tab.svg
app/core/src/main/resources/static/pdfium/
app/core/src/main/resources/static/pdfjs/
app/core/src/main/resources/static/vendor/
app/core/src/main/resources/static/**/*.gz
app/core/src/main/resources/static/**/*.br
app/core/src/main/resources/static/css/cookieconsent.css
app/core/src/main/resources/static/css/cookieconsentCustomisation.css
app/core/src/main/resources/static/mockServiceWorker.js
app/core/src/main/resources/static/js/thirdParty/cookieconsent.umd.js
app/core/src/main/resources/static/images/google-drive.svg
# Note: Keep backend-managed files like fonts/, css/, js/, pdfjs/, etc.
# Gradle
+5
View File
@@ -27,3 +27,8 @@ app/core/src/main/java/stirling/software/SPDF/pdf/signature/CreateSignatureBase.
# Supabase publishable key (public by design, RLS-protected) used as a CI fallback
# default in the tauri-build workflow when the GitHub secret is unset - not a real secret.
.github/workflows/tauri-build.yml:generic-api-key:402
# Staging Supabase publishable key (public by design). Ignored here rather than with an
# inline gitleaks:allow because a trailing comment in a .properties file is part of the
# value, so the pragma would end up inside the key.
app/saas/src/main/resources/application-staging.properties:generic-api-key:16
+49 -6
View File
@@ -57,16 +57,57 @@ tasks:
- cmd: ./gradlew clean bootRun -PbuildWithFrontend=true
platforms: [linux, darwin]
# SaaS backend. dev:saas -> the PR's preview branch, staging:saas -> shared v3,
# PROFILES=none -> production against your own SAAS_DB_*. Production has no named
# task on purpose. Use `none`, not an empty value: Go template `default` treats ""
# as absent and would resolve back to dev.
dev:saas:
desc: "Start backend in SaaS flavor against Supabase"
# `dotenv:` reads from the root Taskfile's directory (".") because this
# subtaskfile is included with `dir: .`.
desc: "Start SaaS backend against the current PR's Supabase preview branch"
dotenv: ['app/.env.saas.local', 'app/.env.saas']
vars:
PROFILES: '{{.PROFILES | default "dev"}}'
cmds:
# Don't move this check into a `sh:` var: dotenv is visible in cmds but not
# during var evaluation, so the test would always see an empty value.
- cmd: |
if [ "{{.PROFILES}}" = "dev" ] && [ -z "${SAAS_DEV_PROJECT_REF:-}" ]; then
echo ">> SAAS_DEV_PROJECT_REF is not set."
echo ">> Testing a SaaS PR? Put its ref, DB password and publishable key in app/.env.saas.local."
echo ">> Wanted the shared v3 project? Use 'task backend:staging:saas' instead."
exit 1
fi
- task: _run:saas
vars:
PORT: '{{.PORT}}'
PROFILES: '{{.PROFILES}}'
AIENGINE_URL: '{{.AIENGINE_URL}}'
AIENGINE_ENABLED: '{{.AIENGINE_ENABLED}}'
AIENGINE_TIMEOUTSECONDS: '{{.AIENGINE_TIMEOUTSECONDS}}'
staging:saas:
desc: "Start SaaS backend against the shared v3 staging project"
cmds:
- task: _run:saas
vars:
PORT: '{{.PORT}}'
PROFILES: staging
AIENGINE_URL: '{{.AIENGINE_URL}}'
AIENGINE_ENABLED: '{{.AIENGINE_ENABLED}}'
AIENGINE_TIMEOUTSECONDS: '{{.AIENGINE_TIMEOUTSECONDS}}'
_run:saas:
internal: true
dotenv: ['app/.env.saas.local', 'app/.env.saas']
ignore_error: true
vars:
PORT: '{{.PORT | default "8080"}}'
# Override to "" to run the pure `saas` profile against your own SAAS_DB_*.
PROFILES: '{{.PROFILES | default "dev"}}'
# Built here rather than inline in the cmds below: the Windows line is an
# unquoted YAML scalar wrapping a cmd.exe string, so a nested {{if ne .X
# "none"}} needs escaped quotes that reach the Go template as literal
# backslashes and fail with `unexpected "\" in operand`.
PROFILE_ARGS: '{{if ne .PROFILES "none"}}--spring.profiles.include={{.PROFILES}}{{end}}'
AIENGINE_URL: '{{.AIENGINE_URL | default ""}}'
AIENGINE_ENABLED: '{{.AIENGINE_ENABLED | default "false"}}'
AIENGINE_TIMEOUTSECONDS: '{{.AIENGINE_TIMEOUTSECONDS | default "120"}}'
@@ -77,9 +118,11 @@ tasks:
AIENGINE_ENABLED: '{{.AIENGINE_ENABLED}}'
AIENGINE_TIMEOUTSECONDS: '{{.AIENGINE_TIMEOUTSECONDS}}'
cmds:
- cmd: cmd /c ".\gradlew.bat :stirling-pdf:bootRun {{if .PROFILES}}--args=\"--spring.profiles.include={{.PROFILES}}\"{{end}}"
# PROFILE_ARGS is empty when PROFILES=none, i.e. the bare `saas` profile
# against SAAS_DB_* (production).
- cmd: cmd /c ".\gradlew.bat :stirling-pdf:bootRun {{if .PROFILE_ARGS}}--args=\"{{.PROFILE_ARGS}}\"{{end}}"
platforms: [windows]
- cmd: ./gradlew :stirling-pdf:bootRun {{if .PROFILES}}--args='--spring.profiles.include={{.PROFILES}}'{{end}}
- cmd: ./gradlew :stirling-pdf:bootRun {{if .PROFILE_ARGS}}--args='{{.PROFILE_ARGS}}'{{end}}
platforms: [linux, darwin]
build:
+44
View File
@@ -0,0 +1,44 @@
version: '3'
tasks:
install:
desc: "Sync the Python environment with the cucumber test dependencies"
run: once
# Deliberately no sources/status fingerprint: the engine venv is shared, so it can
# already exist while synced to a different dependency group. uv no-ops when correct.
cmds:
- uv sync --project ../../engine --locked --group cucumber
run:
desc: "Run the cucumber suite against a running server (BASE_URL, default localhost:8080)"
deps: [install]
cmds:
- uv run --project ../../engine --locked --group cucumber python -m behave --no-capture -f plain {{.CLI_ARGS}}
nightly:
desc: "Run the @nightly cucumber scenarios, excluded from the default run"
summary: |
Heavy LibreOffice/Calibre/Ghostscript conversions. behave.ini excludes @nightly,
so this opts back in explicitly.
Pass extra behave flags via -- :
task cucumber:nightly -- --tags=@convert
deps: [install]
cmds:
- uv run --project ../../engine --locked --group cucumber python -m behave --tags=@nightly --no-capture -f plain {{.CLI_ARGS}}
parallel:
desc: "Run the cucumber suite as concurrent shards against one server (SHARDS, default 10)"
summary: |
Splits the feature files across SHARDS concurrent behave processes hitting a single
backend, to shake out cross-request interference. Auth-coupled features are pinned
to one shard because they change the admin password mid-scenario.
task cucumber:parallel
task cucumber:parallel SHARDS=4
BASE_URL=http://localhost:8081 task cucumber:parallel
deps: [install]
vars:
SHARDS: '{{.SHARDS | default "10"}}'
cmds:
- bash run-parallel.sh {{.SHARDS}} {{if .CLI_ARGS}}-- {{.CLI_ARGS}}{{end}}
+5 -1
View File
@@ -195,7 +195,11 @@ tasks:
# `desktop:build` run `jlink:clean` first to force a fresh build.
- cmd: chmod -R u+w runtime/jre
platforms: [linux, darwin]
- cmd: powershell -NoProfile -Command "Get-ChildItem -Recurse runtime/jre | ForEach-Object { $_.IsReadOnly = $false }"
# Single-quoted so Task's shell leaves `$_` and `$false` alone. Double
# quotes let it expand them as its own variables, and since neither is
# set the command PowerShell actually received was
# `ForEach-Object { .IsReadOnly = }`, which fails on every file.
- cmd: powershell -NoProfile -Command 'Get-ChildItem -Recurse -File runtime/jre | ForEach-Object { $_.IsReadOnly = $false }'
platforms: [windows]
status:
- test -f runtime/jre/release
+83 -19
View File
@@ -5,6 +5,14 @@ version: '3'
# mode flag) or use `--project editor/...` for tsc — so the editor lives
# under frontend/editor/ without each task needing a cd.
vars:
# Dev-only browser-tab label so concurrent worktrees are distinguishable. Only
# the worktree folder basename (e.g. "wt1") is exposed — never the full path,
# hostname, or user. Dropped from production builds.
DEV_LABEL:
sh: >-
{{if eq OS "windows"}}powershell -NoProfile -Command '$root = git rev-parse --show-toplevel 2>$null; if (-not $root) { $root = (Get-Location).Path }; Split-Path -Leaf $root'{{else}}basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)"{{end}}
tasks:
install:
desc: "Install dependencies"
@@ -80,16 +88,52 @@ tasks:
OPEN: '{{.OPEN | default ""}}'
env:
BACKEND_URL: '{{.BACKEND_URL}}'
# Dev-only browser-tab label so concurrent worktrees are distinguishable.
# Only the worktree folder basename (e.g. "wt1") is exposed — never the
# full path, hostname, or user. Consumed at dev-serve time by vite.config
# and dropped from production builds.
STIRLING_DEV_LABEL:
sh: >-
{{if eq OS "windows"}}powershell -NoProfile -Command '$root = git rev-parse --show-toplevel 2>$null; if (-not $root) { $root = (Get-Location).Path }; Split-Path -Leaf $root'{{else}}basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)"{{end}}
STIRLING_DEV_LABEL: '{{.DEV_LABEL}}'
cmds:
- npx vite editor --mode {{.MODE}} --port {{.PORT}}{{if .OPEN}} --open{{end}}
# Separate from dev:_run rather than a flag on it: Task sets an `env:` key even
# when its value resolves to empty, and Vite treats an empty process.env VITE_* as
# authoritative over the committed editor/.env, so folding these in blanks Supabase
# config for the core, proprietary and desktop dev servers.
dev:_run:saas:
internal: true
ignore_error: true
# The backend's own env files, so both halves target one project. Paths are
# relative to this taskfile's dir, `frontend`.
dotenv: ['../app/.env.saas.local', '../app/.env.saas']
vars:
PORT: '{{.PORT | default "5173"}}'
BACKEND_URL: '{{.BACKEND_URL | default "http://localhost:8080"}}'
OPEN: '{{.OPEN | default ""}}'
SAAS_ENV: '{{.SAAS_ENV | default "dev"}}'
env:
BACKEND_URL: '{{.BACKEND_URL}}'
STIRLING_DEV_LABEL: '{{.DEV_LABEL}}'
SAAS_ENV: '{{.SAAS_ENV}}'
# A real process.env VITE_* beats a committed .env in Vite (loadEnv applies
# process.env last), which is what lets this override editor/.env.
#
# These must stay `sh:`, not Go templates: dotenv values are visible to Task's
# embedded shell but not to templates, where {{.SAAS_DEV_PROJECT_REF}} is
# always empty.
VITE_SUPABASE_URL:
sh: |
case "${SAAS_ENV:-dev}" in
staging) ref="${SAAS_STAGING_PROJECT_REF:?set it in app/.env.saas.local}" ;;
*) ref="${SAAS_DEV_PROJECT_REF:?set it in app/.env.saas.local, or run task staging:saas}" ;;
esac
echo "https://${ref}.supabase.co"
VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY:
sh: |
case "${SAAS_ENV:-dev}" in
staging) echo "${SAAS_STAGING_PUBLISHABLE_KEY:?set it in app/.env.saas.local}" ;;
*) echo "${SAAS_DEV_PUBLISHABLE_KEY:?set it in app/.env.saas.local}" ;;
esac
cmds:
- 'echo ">> frontend Supabase target: $VITE_SUPABASE_URL"'
- npx vite editor --mode saas --port {{.PORT}}{{if .OPEN}} --open{{end}}
dev:
desc: "Start frontend dev server"
cmds:
@@ -111,13 +155,23 @@ tasks:
vars: { MODE: proprietary, PORT: '{{.PORT}}', BACKEND_URL: '{{.BACKEND_URL}}', OPEN: '{{.OPEN}}' }
dev:saas:
desc: "Start frontend dev server in SaaS mode"
desc: "Start frontend dev server in SaaS mode (SAAS_ENV=dev|staging|prod)"
deps:
- task: prepare
vars: { MODE: saas }
vars:
SAAS_ENV: '{{.SAAS_ENV | default "dev"}}'
# prod routes to the plain runner, which sets no VITE_SUPABASE_* and so leaves
# the committed editor/.env alone.
RUNNER: '{{if eq .SAAS_ENV "prod"}}dev:_run{{else}}dev:_run:saas{{end}}'
cmds:
- task: dev:_run
vars: { MODE: saas, PORT: '{{.PORT}}', BACKEND_URL: '{{.BACKEND_URL}}', OPEN: '{{.OPEN}}' }
- task: '{{.RUNNER}}'
vars:
MODE: saas
PORT: '{{.PORT}}'
BACKEND_URL: '{{.BACKEND_URL}}'
OPEN: '{{.OPEN}}'
SAAS_ENV: '{{.SAAS_ENV}}'
dev:desktop:
desc: "Start frontend dev server in desktop mode"
@@ -210,15 +264,26 @@ tasks:
# task frontend:storybook:test -- Button
- npx vitest run --config .storybook/vitest.config.ts {{.CLI_ARGS}}
storybook:a11y:
desc: "a11y regression gate over every story, light and dark: fail only on NEW axe violations"
storybook:a11y:light:
desc: "a11y gate over every story in light mode"
deps: [prepare, storybook:browser]
cmds:
- node .storybook/a11y-scan.mjs
- node .storybook/a11y-scan.mjs {{.CLI_ARGS}}
- node .storybook/a11y-check.mjs --in .a11y-scan --manifest .a11y-scan/manifest.txt
- SCAN_THEME=dark node .storybook/a11y-scan.mjs
storybook:a11y:dark:
desc: "a11y gate over every story in dark mode"
deps: [prepare, storybook:browser]
cmds:
- SCAN_THEME=dark node .storybook/a11y-scan.mjs {{.CLI_ARGS}}
- node .storybook/a11y-check.mjs --in .a11y-scan --manifest .a11y-scan/manifest.txt --baseline .storybook/a11y-baseline.dark.json
storybook:a11y:
desc: "a11y gate over every story, light and dark"
cmds:
- task: storybook:a11y:light
- task: storybook:a11y:dark
storybook:a11y:changed:
desc: "a11y gate over the stories this branch affects (default base origin/main)"
summary: |
@@ -233,7 +298,6 @@ tasks:
Pass a base ref through CLI_ARGS, e.g.
task frontend:storybook:a11y:changed -- origin/release
deps: [prepare, storybook:browser]
vars:
BASE: '{{.CLI_ARGS | default "origin/main"}}'
CHANGED:
@@ -244,10 +308,10 @@ tasks:
echo "a11y: no story files affected vs {{.BASE}} — nothing to check"
exit 0
fi
node .storybook/a11y-scan.mjs {{.CHANGED}}
node .storybook/a11y-check.mjs --in .a11y-scan --manifest .a11y-scan/manifest.txt
SCAN_THEME=dark node .storybook/a11y-scan.mjs {{.CHANGED}}
node .storybook/a11y-check.mjs --in .a11y-scan --manifest .a11y-scan/manifest.txt --baseline .storybook/a11y-baseline.dark.json
rc=0
task frontend:storybook:a11y:light -- {{.CHANGED}} || rc=1
task frontend:storybook:a11y:dark -- {{.CHANGED}} || rc=1
exit $rc
storybook:a11y:record:
desc: "Re-record both a11y baselines (run after intentionally fixing/adding violations)"
+76 -20
View File
@@ -73,40 +73,52 @@
"stirling",
],
"java.project.resourceFilters": [
".cache/",
".claude/",
".devcontainer/",
".git/",
".git-blame-ignore-revs",
".gitattributes",
".github/",
".gitignore",
".gradle/",
".pre-commit-config.yaml",
".task/",
".taskfiles/",
".venv/",
".venv*/",
".vscode/",
"bin/",
"app/core/bin/",
"app/.gitignore",
"app/build/",
"app/common/.gitignore",
"app/common/bin/",
"app/proprietary/bin/",
"build/",
"app/core/build/",
"app/common/build/",
"app/proprietary/build/",
"configs/",
"app/core/.gitignore",
"app/core/bin/",
"app/core/configs/",
"customFiles/",
"app/core/customFiles/",
"app/core/LOCAL_APPDATA_FONTCONFIG_CACHE/",
"app/core/logs/",
"app/core/pipeline/",
"app/core/storage/",
"app/proprietary/.gitignore",
"app/proprietary/bin/",
"app/proprietary/storage/",
"app/saas/.gitignore",
"app/saas/bin/",
"app/saas/build/",
"bin/",
"build/",
"devGuide/",
"devTools/",
"docker/",
"docs/",
"exampleYmlFiles",
"engine/",
"frontend/",
"gradle/",
"images/",
"logs/",
"pipeline/",
"scripts/",
"testings/",
".git-blame-ignore-revs",
".gitattributes",
".gitignore",
"app/core/.gitignore",
"app/common/.gitignore",
"app/proprietary/.gitignore",
".pre-commit-config.yaml",
],
// Enables signature help in Java.
"java.signatureHelp.enabled": true,
@@ -135,13 +147,57 @@
"html.format.indentHandlebars": true,
"html.format.preserveNewLines": true,
"html.format.maxPreserveNewLines": 2,
"stylelint.configFile": "devTools/.stylelintrc.json",
"stylelint.configFile": "${workspaceFolder}/devTools/.stylelintrc.json",
"css.lint.unknownAtRules": "ignore",
"scss.lint.unknownAtRules": "ignore",
"less.lint.unknownAtRules": "ignore",
"java.project.sourcePaths": [
"app/core/src/main/java",
"app/common/src/main/java",
"app/proprietary/src/main/java"
],
"[javascript]": {
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit"
}
},
"[javascriptreact]": {
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit"
}
},
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
"editor.defaultFormatter": "vscode.typescript-language-features",
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit"
}
},
"[typescriptreact]": {
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit"
}
},
"oxc.enable.oxlint": true,
"oxc.enable.oxfmt": false,
"oxc.configPath": "frontend/oxlint.config.ts",
"oxc.requireConfig": true,
"oxc.lint.run": "onType",
"oxc.fixKind": "safe_fix",
"[toml]": {
"editor.defaultFormatter": "tamasfe.even-better-toml",
// Keep TOML formatting compatible with .editorconfig and the pre-commit
// locale sorter. Key ordering itself is handled by task pre-commit:toml-sort.
"editor.insertSpaces": true,
"editor.tabSize": 4,
"editor.rulers": [127],
"evenBetterToml.formatter.alignEntries": false,
"evenBetterToml.formatter.alignComments": false,
"evenBetterToml.formatter.indentString": " ",
"evenBetterToml.formatter.columnWidth": 127,
"evenBetterToml.formatter.reorderKeys": false,
"evenBetterToml.formatter.reorderArrays": false,
"evenBetterToml.formatter.reorderInlineTables": false,
"evenBetterToml.formatter.trailingNewline": true,
"evenBetterToml.formatter.crlf": false
}
}
+7 -4
View File
@@ -2,6 +2,13 @@
Thank you for your interest in contributing to Stirling-PDF! There are many ways to contribute other than writing code. For example, reporting bugs, creating suggestions, and adding or modifying translations.
## License
By contributing to this project, you agree that your contributions will be licensed under the project [license](LICENSE), which follows an open-core model.
The codebase is a mix of MIT and source-available code, so your contribution is licensed according to the directory it is committed to.
PRs are welcome in any directory by any user, just be aware of which license applies to the code you change.
## Issue Guidelines
Issues can be used to report bugs, request features, or ask questions. If you have a question, you could also ask us in our [Discord](https://discord.gg/FJUSXUSYec).
@@ -63,7 +70,3 @@ For technical guides, setup instructions, and development resources:
For configuration and usage guides, see:
- [Database Guide](DATABASE.md) - Database setup and configuration
- [OCR Guide](HowToUseOCR.md) - OCR setup and configuration
## License
By contributing to this project, you agree that your contributions will be licensed under the [MIT License](LICENSE).
+2 -2
View File
@@ -46,8 +46,8 @@ This guide focuses on developing for Stirling 2.0, including both the React fron
- Docker
- Git
- Java JDK 25
- Node.js 18+ and npm (required for frontend development)
- Gradle 7.0 or later (Included within the repo)
- Node.js 22+ and npm (required for frontend development)
- Gradle 9.0 or later (Included within the repo)
- [uv](https://docs.astral.sh/uv/) — Python package manager (required for engine development)
- Rust and Cargo (required for Tauri desktop app development)
- Tauri CLI (install with `cargo install tauri-cli`)
+21 -3
View File
@@ -25,6 +25,9 @@ includes:
e2e:
taskfile: .taskfiles/e2e.yml
dir: .
cucumber:
taskfile: .taskfiles/cucumber.yml
dir: testing/cucumber
pre-commit:
taskfile: .taskfiles/pre-commit.yml
dir: .
@@ -96,11 +99,22 @@ tasks:
BACKEND_URL: 'http://localhost:{{.BACKEND_PORT}}'
OPEN: "true"
# Set SAAS_DEV_PROJECT_REF in app/.env.saas.local to pick the PR.
dev:saas:
desc: "Start SaaS backend + frontend concurrently on free ports"
desc: "Start SaaS backend + frontend + engine against the current PR's preview branch"
cmds:
- task: dev:_all
vars: { FRONTEND: saas, BACKEND: saas }
vars: { FRONTEND: saas, BACKEND: saas, SAAS_ENV: dev }
staging:saas:
desc: "Start SaaS backend + frontend + engine against the shared v3 staging project"
cmds:
- task: dev:_all
vars:
FRONTEND: saas
BACKEND: saas
BACKEND_TASK: backend:staging:saas
SAAS_ENV: staging
dev:all:
desc: "Start backend + frontend + engine concurrently on free ports"
@@ -112,6 +126,9 @@ tasks:
vars:
FRONTEND: '{{.FRONTEND | default "proprietary"}}'
BACKEND: '{{.BACKEND | default "proprietary"}}'
BACKEND_TASK: '{{.BACKEND_TASK | default (printf "backend:dev:%s" .BACKEND)}}'
# Only meaningful to the saas frontend; every other flavor ignores it.
SAAS_ENV: '{{.SAAS_ENV | default ""}}'
PORTS:
sh: '{{if eq OS "windows"}}{{.FIND_FREE_PORT_PS}} 8080 5173 5001{{else}}{{.FIND_FREE_PORT_SH}} 8080 5173 5001{{end}}'
BACKEND_PORT: '{{index (splitList "\n" .PORTS) 0}}'
@@ -121,7 +138,7 @@ tasks:
- task: engine:dev
vars:
PORT: '{{.ENGINE_PORT}}'
- task: 'backend:dev:{{.BACKEND}}'
- task: '{{.BACKEND_TASK}}'
vars:
PORT: '{{.BACKEND_PORT}}'
AIENGINE_URL: 'http://localhost:{{.ENGINE_PORT}}'
@@ -131,6 +148,7 @@ tasks:
PORT: '{{.FRONTEND_PORT}}'
BACKEND_URL: 'http://localhost:{{.BACKEND_PORT}}'
OPEN: "true"
SAAS_ENV: '{{.SAAS_ENV}}'
# ============================================================
# Build
+33 -38
View File
@@ -4,6 +4,11 @@ This guide explains how to set up Windows code signing for Stirling-PDF desktop
## Overview
Releases are signed with **DigiCert KeyLocker**, a cloud HSM: the private key never
leaves DigiCert, and the runner signs through a PKCS#11 provider. The older approach
of uploading a base64 `.pfx` to a repository secret has been removed from the
workflows - the sections below describe KeyLocker, which is what actually runs.
Windows code signing is essential for:
- Preventing Windows SmartScreen warnings
- Building trust with users
@@ -49,29 +54,19 @@ openssl pkcs12 -export -out certificate.pfx -inkey private-key.key -in certifica
### Required Secrets
Navigate to your GitHub repository → Settings → Secrets and variables → Actions
Navigate to your GitHub repository → Settings → Environments → `release-signing`.
Add the following secrets:
These live in the `release-signing` environment, not at repository scope. That
environment requires reviewer approval and is limited to `main`, `release`,
`hotfix/*` and `v*` tags. All five come from the DigiCert ONE console.
#### 1. `WINDOWS_CERTIFICATE`
- **Description**: Base64-encoded .pfx certificate file
- **How to create**:
**On macOS/Linux:**
```bash
base64 -i certificate.pfx | pbcopy # Copies to clipboard
```
**On Windows (PowerShell):**
```powershell
[Convert]::ToBase64String([IO.File]::ReadAllBytes("certificate.pfx")) | Set-Clipboard
```
Paste the entire base64 string into the GitHub secret.
#### 2. `WINDOWS_CERTIFICATE_PASSWORD`
- **Description**: Password for the .pfx certificate
- **Value**: The password you set when creating/exporting the .pfx file
| Secret | Description |
| --- | --- |
| `SM_API_KEY` | KeyLocker API key. Also acts as the on/off switch: signing steps are gated on it being non-empty. |
| `SM_CLIENT_CERT_FILE_B64` | Base64-encoded PKCS#12 client authentication certificate. |
| `SM_CLIENT_CERT_PASSWORD` | Password for that client certificate. |
| `SM_KEYPAIR_ALIAS` | Alias of the signing keypair to use. |
| `SM_HOST` | DigiCert ONE host, e.g. `https://clientauth.one.digicert.com`. |
### Optional Secrets for Tauri Updater
@@ -110,23 +105,23 @@ The Windows signing configuration is already set up:
### 2. GitHub Workflow (.github/workflows/tauri-build.yml)
The workflow includes three Windows signing steps:
The workflow includes four Windows signing steps, all gated on `SM_API_KEY` being
set and the ref being the release branch:
1. **Import Certificate**: Decodes and imports the .pfx certificate into Windows certificate store
2. **Build Tauri App**: Builds and signs the application using the imported certificate
3. **Verify Signature**: Validates that both .exe and .msi files are properly signed
1. **Setup DigiCert KeyLocker**: Installs the DigiCert signing tools via `digicert/ssm-code-signing`
2. **Setup DigiCert KeyLocker Certificate**: Writes the client cert and exports the PKCS#11 config
3. **Configure Windows code signing / Build Tauri app**: Signs through the PKCS#11 provider
4. **Verify Windows Code Signature**: Validates that the .exe and .msi are properly signed
## Testing the Setup
### 1. Local Testing (Windows Only)
Before pushing to GitHub, test locally:
KeyLocker is CI-only. To check signing locally, install your own certificate into
the Windows store and point Tauri at it; the build no longer reads any certificate
from an environment variable.
```powershell
# Set environment variables
$env:WINDOWS_CERTIFICATE = [Convert]::ToBase64String([IO.File]::ReadAllBytes("certificate.pfx"))
$env:WINDOWS_CERTIFICATE_PASSWORD = "your-certificate-password"
# Build the application
cd frontend
npm run tauri build
@@ -191,9 +186,10 @@ Look for:
- Consider EV certificate for immediate reputation
### Certificate Not Found During Build
- Verify `WINDOWS_CERTIFICATE` secret is set
- Check base64 encoding is correct (no extra whitespace)
- Ensure password is correct
- Verify `SM_API_KEY` is present in the `release-signing` environment. If it is empty
the signing steps skip silently and the build succeeds unsigned.
- Check `SM_CLIENT_CERT_FILE_B64` base64 encoding is correct (no extra whitespace)
- Ensure `SM_CLIENT_CERT_PASSWORD` and `SM_KEYPAIR_ALIAS` match the DigiCert keypair
## Security Best Practices
@@ -220,11 +216,10 @@ Look for:
## Certificate Lifecycle
### Before Expiration
1. Obtain new certificate from CA (typically annual renewal)
2. Convert to .pfx format if needed
3. Update `WINDOWS_CERTIFICATE` secret with new base64-encoded certificate
4. Update `WINDOWS_CERTIFICATE_PASSWORD` if password changed
5. Test build to verify new certificate works
1. Renew the certificate in the DigiCert ONE console (typically annual)
2. If the keypair alias changed, update `SM_KEYPAIR_ALIAS` in the `release-signing` environment
3. If the client authentication certificate was reissued, update `SM_CLIENT_CERT_FILE_B64` and `SM_CLIENT_CERT_PASSWORD`
4. Test build to verify the new certificate works
### Expired Certificates
- Signed binaries remain valid (timestamp proves signing time)
+35 -17
View File
@@ -1,15 +1,16 @@
###############################################################################
# Stirling-PDF SaaS environment defaults.
# Stirling-PDF SaaS environment defaults. Committed, non-secret. Real values for secrets go in
# .env.saas.local, which is loaded first and wins. Do not commit that file.
#
# This file is committed and provides non-secret defaults loaded by
# `task backend:dev:saas`. Put real values for secrets (passwords, project
# refs, edge function secrets) in `.env.saas.local` - any variable set there
# takes precedence over what's defined here.
# Three environments, each deriving its Supabase URLs, JWT issuer and JWKS from one project ref:
#
# DO NOT commit `.env.saas.local`. Only `.env.saas` is checked in.
###############################################################################
# prod PROFILES=none SAAS_DB_* the live project
# staging PROFILES=staging SAAS_STAGING_* pinned to v3, always there
# dev PROFILES=dev SAAS_DEV_* follows a SaaS PR's preview branch
#
# dev is the default for `task backend:dev:saas`. Use staging for somewhere stable; use dev when
# testing an open SaaS PR, since its preview branch is the only place those migrations are applied.
# ---------- Supabase project ----------
# ---------- Supabase project (prod / no-profile) ----------
# Project reference (the subdomain part of <ref>.supabase.co). Required.
# Set in .env.saas.local.
SAAS_DB_PROJECT_REF=
@@ -17,18 +18,35 @@ SAAS_DB_PROJECT_REF=
# Edge function secret used by billing/license rollup calls. Set in .env.saas.local.
SUPABASE_EDGE_FUNCTION_SECRET=
# ---------- Database (saas profile) ----------
# Direct JDBC URL to the Supabase Postgres. Required when running the plain
# `saas` profile (i.e. without `--spring.profiles.include=dev`).
# ---------- Database (no profile) ----------
# Direct JDBC URL to the Supabase Postgres. Required when running without
# `--spring.profiles.include=...`.
# Example: jdbc:postgresql://db.<project-ref>.supabase.co:5432/postgres
SAAS_DB_URL=
SAAS_DB_USERNAME=postgres
SAAS_DB_PASSWORD=
# ---------- Database (dev profile overrides) ----------
# Used when `--spring.profiles.include=dev` is active. The dev profile
# defaults the URL/username to the shared dev Supabase project, but the
# password must still be provided in .env.saas.local.
SAAS_DEV_DB_URL=
# ---------- staging profile ----------
# The shared long-lived v3 project. application-staging.properties defaults the ref,
# URL, database host and meter endpoint, so staging needs only the password, in
# .env.saas.local. Set SAAS_STAGING_PROJECT_REF to repoint it; everything derives.
#
# The ref and publishable key are duplicated here because the task derives the
# frontend's VITE_SUPABASE_* from them and a shell cannot read a Spring default.
# Neither is secret: the ref is a public subdomain, the key ships in the bundle.
SAAS_STAGING_PROJECT_REF=qacaivhsjtftfwtgjvva
SAAS_STAGING_PUBLISHABLE_KEY=sb_publishable_nIM8y-9ARPE7EzQwAQHKMg_40fCN6kY # gitleaks:allow
SAAS_STAGING_DB_USERNAME=postgres
SAAS_STAGING_DB_PASSWORD=
# ---------- dev profile ----------
# The SaaS PR's Supabase preview branch. Take the ref from that PR's "Supabase
# Preview" check; the profile derives URL, JWT issuer, JWKS, meter endpoint and
# database host from it, so this one value follows a different PR.
#
# A preview branch has its own password and keys; the parent project's will not
# authenticate. Both go in .env.saas.local, along with the ref.
SAAS_DEV_PROJECT_REF=
SAAS_DEV_PUBLISHABLE_KEY=
SAAS_DEV_DB_USERNAME=postgres
SAAS_DEV_DB_PASSWORD=
+2 -2
View File
@@ -21,8 +21,8 @@ dependencies {
api 'org.snakeyaml:snakeyaml-engine:3.0.1'
api "org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.3"
// Simple Java Mail for EML/MSG parsing (replaces direct Angus Mail usage)
api 'org.simplejavamail:simple-java-mail:9.2.0'
api 'org.simplejavamail:outlook-module:9.2.0' // MSG file support
api 'org.simplejavamail:simple-java-mail:9.3.1'
api 'org.simplejavamail:outlook-module:9.3.1' // MSG file support
api 'jakarta.mail:jakarta.mail-api:2.1.5'
runtimeOnly 'org.eclipse.angus:angus-mail:2.0.5'
@@ -6,6 +6,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@@ -13,6 +14,7 @@ import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.service.PdfaLevelAServiceInterface;
@Service
@Slf4j
@@ -51,12 +53,16 @@ public class EndpointConfiguration {
private Map<String, DisableReason> groupDisableReasons = new ConcurrentHashMap<>();
private Map<String, Set<String>> endpointAlternatives = new ConcurrentHashMap<>();
private final boolean runningProOrHigher;
private final boolean pdfUaAvailable;
public EndpointConfiguration(
ApplicationProperties applicationProperties,
@Qualifier("runningProOrHigher") boolean runningProOrHigher) {
@Qualifier("runningProOrHigher") boolean runningProOrHigher,
@Autowired(required = false) PdfaLevelAServiceInterface pdfaLevelAService) {
this.applicationProperties = applicationProperties;
this.runningProOrHigher = runningProOrHigher;
// The PDF/UA tagger ships in the proprietary module, and so do its endpoints.
this.pdfUaAvailable = pdfaLevelAService != null;
init();
processEnvironmentConfigs();
}
@@ -356,6 +362,7 @@ public class EndpointConfiguration {
addEndpointToGroup("Convert", "pdf-to-img");
addEndpointToGroup("Convert", "img-to-pdf");
addEndpointToGroup("Convert", "pdf-to-pdfa");
addEndpointToGroup("Convert", "pdf-to-ua");
addEndpointToGroup("Convert", "file-to-pdf");
addEndpointToGroup("Convert", "pdf-to-word");
addEndpointToGroup("Convert", "pdf-to-presentation");
@@ -395,6 +402,7 @@ public class EndpointConfiguration {
// Backend-only endpoints (not in frontend tool registry endpoints)
addEndpointToGroup("Security", "redact");
addEndpointToGroup("Security", "verify-pdf");
addEndpointToGroup("Security", "accessibility-report");
addEndpointToGroup("Security", "sign");
// Adding endpoints to "Other" group
@@ -529,6 +537,8 @@ public class EndpointConfiguration {
addEndpointToGroup("Java", "json-to-pdf");
addEndpointToGroup("Java", "pdf-to-video");
addEndpointToGroup("Java", "verify-pdf");
addEndpointToGroup("Java", "pdf-to-ua");
addEndpointToGroup("Java", "accessibility-report");
addEndpointToGroup("Java", "flatten");
addEndpointToGroup("Java", "unlock-pdf-forms");
addEndpointToGroup("Java", "validate-signature");
@@ -600,6 +610,8 @@ public class EndpointConfiguration {
// veraPDF dependent endpoints
addEndpointToGroup("veraPDF", "verify-pdf");
addEndpointToGroup("veraPDF", "pdf-to-ua");
addEndpointToGroup("veraPDF", "accessibility-report");
// Pdftohtml dependent endpoints
addEndpointToGroup("Pdftohtml", "pdf-to-html");
@@ -630,6 +642,11 @@ public class EndpointConfiguration {
disableGroup("enterprise");
}
if (!pdfUaAvailable) {
disableEndpoint("pdf-to-ua");
disableEndpoint("accessibility-report");
}
if (!applicationProperties.getSystem().isEnableUrlToPDF()) {
disableEndpoint("url-to-pdf");
}
@@ -2,7 +2,9 @@ package stirling.software.common.aop;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
@@ -273,6 +275,7 @@ public class AutoJobAspect {
// Store the fileId for later reference
pdfFile.setFileId(fileId);
recordPendingInputFile(fileId);
// Replace the original MultipartFile with our persistent copy
MultipartFile persistentFile = fileStorage.retrieveFile(fileId);
@@ -290,6 +293,29 @@ public class AutoJobAspect {
return originalArgs;
}
/**
* Queue an input copy for attribution to the job. The job id does not exist yet at this point,
* so {@link JobExecutorService} drains this list once it mints one.
*/
@SuppressWarnings("unchecked")
private void recordPendingInputFile(String fileId) {
try {
Object existing = request.getAttribute(JobExecutorService.PENDING_INPUT_FILE_IDS_ATTR);
List<String> ids;
if (existing instanceof List<?> list) {
ids = (List<String>) list;
} else {
ids = new ArrayList<>();
request.setAttribute(JobExecutorService.PENDING_INPUT_FILE_IDS_ATTR, ids);
}
ids.add(fileId);
} catch (RuntimeException ex) {
// Without a bound request the copy cannot be attributed; the periodic sweep is the
// only backstop, so make the miss visible rather than silently leaking the file.
log.warn("Could not record input copy {} for cleanup: {}", fileId, ex.getMessage());
}
}
private String getJobIdFromContext() {
try {
return (String) request.getAttribute("jobId");
@@ -73,7 +73,7 @@ public class RuntimePathConfig {
defaultWatchedFolders,
watchedFoldersDirs,
pipeline != null ? pipeline.getWatchedFoldersDir() : null);
this.pipelineWatchedFoldersPath = this.pipelineWatchedFoldersPaths.get(0);
this.pipelineWatchedFoldersPath = this.pipelineWatchedFoldersPaths.getFirst();
this.pipelineFinishedFoldersPath =
resolvePath(
defaultFinishedFolders,
@@ -1046,6 +1046,8 @@ public class ApplicationProperties {
// 'https://app.example.com'). If not set, falls back to backendUrl.
private boolean enableMobileScanner = true; // Enable mobile phone QR code upload feature
private boolean enableMobileSignature =
true; // Enable drawing signatures on a phone via QR code
private MobileScannerSettings mobileScannerSettings = new MobileScannerSettings();
private ServerCertificate serverCertificate = new ServerCertificate();
@@ -1314,7 +1316,7 @@ public class ApplicationProperties {
public static class Ui {
private String appNameNavbar;
private List<String> languages;
private String logoStyle = "classic"; // Options: "classic" (default) or "modern"
private String logoStyle = "modern"; // Options: "modern" (default) or "classic"
private boolean defaultHideUnavailableTools = false;
private boolean defaultHideUnavailableConversions = false;
private HideDisabledTools hideDisabledTools = new HideDisabledTools();
@@ -1325,10 +1327,10 @@ public class ApplicationProperties {
public String getLogoStyle() {
// Validate and return either "modern" or "classic"
if ("modern".equalsIgnoreCase(logoStyle)) {
return "modern";
if ("classic".equalsIgnoreCase(logoStyle)) {
return "classic";
}
return "classic"; // default
return "modern"; // default
}
@Data
@@ -52,6 +52,13 @@ public class JobResult {
/** Key/value metadata that survives the write-through into the shared job store. */
private final Map<String, String> metadata = new ConcurrentHashMap<>();
/**
* File ids of the persistent input copies made for this job. An async submit copies the upload
* into FileStorage so the job can still read it after the request returns; without tracking
* them here nothing would ever delete those copies.
*/
@JsonIgnore private final List<String> inputFileIds = new CopyOnWriteArrayList<>();
/**
* Create a new JobResult with the given job ID
*
@@ -167,6 +174,22 @@ public class JobResult {
return Collections.unmodifiableList(notes);
}
/** Record a persistent input copy so job cleanup deletes it alongside the results. */
public void addInputFileId(String fileId) {
if (fileId != null && !fileId.isBlank() && !inputFileIds.contains(fileId)) {
this.inputFileIds.add(fileId);
}
}
/**
* File ids of this job's persistent input copies.
*
* @return An unmodifiable view of the input file ids
*/
public List<String> getInputFileIds() {
return Collections.unmodifiableList(inputFileIds);
}
/** Attach a metadata value, e.g. a policy id so cluster peers can identify a policy run. */
public void putMetadata(String key, String value) {
if (key != null && value != null) {
@@ -60,54 +60,40 @@ public class Provider {
}
private UsernameAttribute validateUsernameAttribute(UsernameAttribute usernameAttribute) {
switch (name) {
case "google" -> {
return validateGoogleUsernameAttribute(usernameAttribute);
}
case "github" -> {
return validateGitHubUsernameAttribute(usernameAttribute);
}
case "keycloak" -> {
return validateKeycloakUsernameAttribute(usernameAttribute);
}
default -> {
return usernameAttribute;
}
}
return switch (name) {
case "google" -> validateGoogleUsernameAttribute(usernameAttribute);
case "github" -> validateGitHubUsernameAttribute(usernameAttribute);
case "keycloak" -> validateKeycloakUsernameAttribute(usernameAttribute);
default -> usernameAttribute;
};
}
private UsernameAttribute validateKeycloakUsernameAttribute(
UsernameAttribute usernameAttribute) {
switch (usernameAttribute) {
case EMAIL, NAME, GIVEN_NAME, FAMILY_NAME, PREFERRED_USERNAME -> {
return usernameAttribute;
}
return switch (usernameAttribute) {
case EMAIL, NAME, GIVEN_NAME, FAMILY_NAME, PREFERRED_USERNAME -> usernameAttribute;
default ->
throw new UnsupportedClaimException(
String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));
}
};
}
private UsernameAttribute validateGoogleUsernameAttribute(UsernameAttribute usernameAttribute) {
switch (usernameAttribute) {
case EMAIL, NAME, GIVEN_NAME, FAMILY_NAME -> {
return usernameAttribute;
}
return switch (usernameAttribute) {
case EMAIL, NAME, GIVEN_NAME, FAMILY_NAME -> usernameAttribute;
default ->
throw new UnsupportedClaimException(
String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));
}
};
}
private UsernameAttribute validateGitHubUsernameAttribute(UsernameAttribute usernameAttribute) {
switch (usernameAttribute) {
case LOGIN, EMAIL, NAME -> {
return usernameAttribute;
}
return switch (usernameAttribute) {
case LOGIN, EMAIL, NAME -> usernameAttribute;
default ->
throw new UnsupportedClaimException(
String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));
}
};
}
@Override
@@ -361,8 +361,8 @@ public class PdfMarkdownConverter {
if (xs.isEmpty()) {
return List.of(lines);
}
float minX = xs.get(0);
float maxX = xs.get(xs.size() - 1);
float minX = xs.getFirst();
float maxX = xs.getLast();
float splitAt = (minX + maxX) / 2f;
float biggestGap = 0;
for (int i = 1; i < xs.size(); i++) {
@@ -492,7 +492,7 @@ public class PdfMarkdownConverter {
List<List<Line>> anchorGroups = new ArrayList<>();
List<Line> current = new ArrayList<>();
current.add(cands.get(0));
current.add(cands.getFirst());
for (int i = 1; i < cands.size(); i++) {
float gap = cands.get(i - 1).y - cands.get(i).y;
if (gap > splitThreshold) {
@@ -513,8 +513,8 @@ public class PdfMarkdownConverter {
if (anchors.size() < 2) {
continue;
}
float top = anchors.get(0).y;
float bottom = anchors.get(anchors.size() - 1).y;
float top = anchors.getFirst().y;
float bottom = anchors.getLast().y;
// Each anchor seeds a row; absorb wrapped continuation lines (non-anchors within the
// run's vertical span, with a little slack below the last row) into the anchor above.
@@ -674,8 +674,8 @@ public class PdfMarkdownConverter {
float minGutter = Math.max(10f, charWidth * 2.5f);
List<float[]> merged = new ArrayList<>();
for (float[] band : columns) {
if (!merged.isEmpty() && band[0] - merged.get(merged.size() - 1)[1] < minGutter) {
merged.get(merged.size() - 1)[1] = band[1];
if (!merged.isEmpty() && band[0] - merged.getLast()[1] < minGutter) {
merged.getLast()[1] = band[1];
} else {
merged.add(new float[] {band[0], band[1]});
}
@@ -734,7 +734,7 @@ public class PdfMarkdownConverter {
}
}
StringBuilder sb = new StringBuilder();
sb.append(buildGfmRow(rows.get(0), widths, cols)).append('\n');
sb.append(buildGfmRow(rows.getFirst(), widths, cols)).append('\n');
sb.append('|');
for (int c = 0; c < cols; c++) {
sb.append('-').append("-".repeat(widths[c])).append('-').append('|');
@@ -910,8 +910,8 @@ public class PdfMarkdownConverter {
}
// Only merge a sentence continuation between two text paragraphs, never into/out of a
// table.
if (!(output.get(output.size() - 1) instanceof String last)
|| !(pageItems.get(0) instanceof String first)) {
if (!(output.getLast() instanceof String last)
|| !(pageItems.getFirst() instanceof String first)) {
return;
}
if (!first.isEmpty()
@@ -932,13 +932,13 @@ public class PdfMarkdownConverter {
for (Object e : elements) {
if (e instanceof TableBlock tb
&& !out.isEmpty()
&& out.get(out.size() - 1) instanceof TableBlock prev
&& out.getLast() instanceof TableBlock prev
&& columnsMatch(flatten(prev.rows()), flatten(tb.rows()))) {
List<List<Line>> merged = new ArrayList<>(prev.rows());
List<List<Line>> tail = tb.rows();
if (!tail.isEmpty()
&& !prev.rows().isEmpty()
&& rowText(tail.get(0)).equals(rowText(prev.rows().get(0)))) {
&& rowText(tail.getFirst()).equals(rowText(prev.rows().getFirst()))) {
tail = tail.subList(1, tail.size());
}
merged.addAll(tail);
@@ -971,7 +971,7 @@ public class PdfMarkdownConverter {
continue;
}
if (e instanceof TableBlock tb && !tb.rows().isEmpty()) {
return rowText(tb.rows().get(0));
return rowText(tb.rows().getFirst());
}
return null;
}
@@ -729,4 +729,32 @@ public class CustomPDFDocumentFactory {
p.toFile().deleteOnExit();
return p;
}
/** A custom RandomAccessRead implementation that deletes the file when closed */
private static class DeletingRandomAccessFile extends RandomAccessReadBufferedFile {
private final Path tempFilePath;
public DeletingRandomAccessFile(File file) throws IOException {
super(file);
this.tempFilePath = file.toPath();
}
@Override
public void close() throws IOException {
try {
super.close();
} finally {
try {
boolean deleted = Files.deleteIfExists(tempFilePath);
if (deleted) {
log.info("Successfully deleted temp file: {}", tempFilePath);
} else {
log.warn("Failed to delete temp file (may not exist): {}", tempFilePath);
}
} catch (IOException e) {
log.error("Error deleting temp file: {}", tempFilePath, e);
}
}
}
}
}
@@ -179,6 +179,21 @@ public class FileStorage {
return fileStore.delete(fileId);
}
/**
* Delete a stored file without the per-file ownership check.
*
* <p>Job cleanup authorises at the job level and then deletes that job's own files, so the
* deleter is legitimately not their owner - an admin sweeping every user's jobs, or the
* unauthenticated scheduled task. Routing those through {@link #deleteFile(String)} makes the
* ownership check throw and silently orphans the files on disk.
*
* <p>Only ever pass file ids read back off a job that the caller has already been authorised
* for; never a caller-supplied id.
*/
public boolean deleteFileAsSystem(String fileId) {
return fileStore.delete(fileId);
}
public boolean fileExists(String fileId) {
enforceOwnership(fileId);
return fileStore.exists(fileId);
@@ -157,7 +157,7 @@ public class InternalApiClient {
boolean hasFilePart =
body.values().stream()
.flatMap(java.util.List::stream)
.anyMatch(v -> v instanceof Resource);
.anyMatch(Resource.class::isInstance);
if (isAiTool && !hasFilePart) {
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
}
@@ -1,6 +1,7 @@
package stirling.software.common.service;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
@@ -33,6 +34,14 @@ import stirling.software.common.util.RegexPatternUtils;
@Slf4j
public class JobExecutorService {
/**
* Request attribute holding the FileStorage ids of persistent input copies made for the job
* about to be created. Populated before the job id exists (the aspect copies the upload while
* processing arguments), drained onto the JobResult as soon as the task is created so cleanup
* can delete them.
*/
public static final String PENDING_INPUT_FILE_IDS_ATTR = "autoJobPendingInputFileIds";
private final TaskManager taskManager;
private final FileStorage fileStorage;
private final HttpServletRequest request;
@@ -133,6 +142,7 @@ public class JobExecutorService {
resourceWeight);
taskManager.createTask(jobId);
registerPendingInputFiles(jobId);
final String capturedJobIdForQueue = jobId;
Supplier<Object> wrappedWork =
@@ -163,6 +173,7 @@ public class JobExecutorService {
return ResponseEntity.ok().body(new JobResponse<>(true, jobId, null));
} else if (async) {
taskManager.createTask(jobId);
registerPendingInputFiles(jobId);
final String capturedJobId = jobId;
@@ -484,4 +495,30 @@ public class JobExecutorService {
}
return baseJobId;
}
/**
* Hand the input copies made while processing arguments to the freshly created job, so job
* cleanup deletes them. Drains the attribute so a retry cannot attribute the same ids twice.
*/
@SuppressWarnings("unchecked")
private void registerPendingInputFiles(String jobId) {
if (request == null) {
return;
}
Object pending;
try {
pending = request.getAttribute(PENDING_INPUT_FILE_IDS_ATTR);
request.removeAttribute(PENDING_INPUT_FILE_IDS_ATTR);
} catch (RuntimeException ex) {
// No request bound to this thread (e.g. an internally dispatched job).
log.debug("Could not read pending input file ids: {}", ex.getMessage());
return;
}
if (!(pending instanceof List<?> ids)) {
return;
}
for (String fileId : (List<String>) ids) {
taskManager.registerInputFile(jobId, fileId);
}
}
}
@@ -0,0 +1,22 @@
package stirling.software.common.service;
import java.util.List;
/**
* Raises a converted PDF/A file from conformance level B to level A, which needs the tagging the
* PDF/UA tagger does. Implemented only in the proprietary module; core builds convert at level B.
*/
public interface PdfaLevelAServiceInterface {
/**
* @param levelA true only when the file was tagged and validated, so the claim is never a guess
*/
record Result(byte[] pdfBytes, boolean levelA, List<String> warnings) {}
/**
* @param part PDF/A part, 1 to 3; part 1 keeps its PDF 1.4 version
* @param alsoDeclareUa additionally claim PDF/UA, but only if it validates
*/
Result upgradeToLevelA(
byte[] pdfBytes, int part, String language, String title, boolean alsoDeclareUa);
}
@@ -17,6 +17,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
@@ -234,6 +235,24 @@ public class TaskManager {
return false;
}
/**
* Record a persistent input copy against a job so cleanup deletes it with the results.
*
* @param jobId The job ID
* @param fileId The FileStorage id of the input copy
* @return true if the job exists and the id was recorded
*/
public boolean registerInputFile(String jobId, String fileId) {
JobResult jobResult = jobResults.get(jobId);
if (jobResult == null) {
log.warn("Attempted to register an input file against non-existent job ID: {}", jobId);
return false;
}
jobResult.addInputFileId(fileId);
log.debug("Registered input file {} for job {}", fileId, jobId);
return true;
}
/** Attach metadata to a job and write it through to the shared store for cluster peers. */
public boolean putMetadata(String jobId, String key, String value) {
JobResult jobResult = jobResults.get(jobId);
@@ -329,25 +348,59 @@ public class TaskManager {
return jobResults.computeIfAbsent(jobId, JobResult::createNew);
}
/**
* What a cleanup pass removed. Returned by the on-demand cleanup so callers can assert on it.
*/
public record CleanupSummary(int jobsRemoved, int filesDeleted, int jobsRetained) {}
/** Clean up old completed job results. No-op in cluster mode; the backplane TTL owns expiry. */
public void cleanupOldJobs() {
public CleanupSummary cleanupOldJobs() {
if (clusterBackplane != null && !clusterBackplane.shouldRunLocalCleanup()) {
return;
return new CleanupSummary(0, 0, jobResults.size());
}
return cleanupJobs(false, jobId -> true);
}
/**
* Force-expire this node's finished jobs now, ignoring the age threshold. Jobs still running
* are left alone - deleting their files mid-flight would break them - and are reported as
* retained.
*
* <p>Unlike {@link #cleanupOldJobs()} this always runs locally: it is an explicit request to
* release this node's storage, not the scheduled sweep the backplane TTL owns.
*
* @param jobIdFilter Only jobs whose id passes this predicate are considered, so a caller can
* restrict the sweep to jobs the requester is allowed to touch
* @return What was removed
*/
public CleanupSummary cleanupFinishedJobsNow(Predicate<String> jobIdFilter) {
return cleanupJobs(true, jobIdFilter);
}
private CleanupSummary cleanupJobs(boolean force, Predicate<String> filter) {
LocalDateTime expiryThreshold =
LocalDateTime.now().minus(jobResultExpiryMinutes, ChronoUnit.MINUTES);
LocalDateTime pendingExpiryThreshold =
LocalDateTime.now().minus(pendingJobExpiryMinutes, ChronoUnit.MINUTES);
int removedCount = 0;
int filesDeleted = 0;
int retainedCount = 0;
try {
for (Map.Entry<String, JobResult> entry : jobResults.entrySet()) {
JobResult result = entry.getValue();
if (!filter.test(entry.getKey())) {
retainedCount++;
continue;
}
boolean expiredCompletedJob =
result.isComplete()
&& result.getCompletedAt() != null
&& result.getCompletedAt().isBefore(expiryThreshold);
&& (force
|| (result.getCompletedAt() != null
&& result.getCompletedAt()
.isBefore(expiryThreshold)));
boolean abandonedPendingJob =
!result.isComplete()
&& result.getCreatedAt() != null
@@ -360,7 +413,7 @@ public class TaskManager {
// Clean up file results
if (expiredCompletedJob) {
cleanupJobFiles(result, entry.getKey());
filesDeleted += cleanupJobFiles(result, entry.getKey());
}
// Remove the job result
@@ -369,15 +422,22 @@ public class TaskManager {
jobStore.delete(entry.getKey());
}
removedCount++;
} else {
retainedCount++;
}
}
if (removedCount > 0) {
log.info("Cleaned up {} expired job results", removedCount);
log.info(
"Cleaned up {} {} job results ({} files deleted)",
removedCount,
force ? "finished" : "expired",
filesDeleted);
}
} catch (Exception e) {
log.error("Error during job cleanup: {}", e.getMessage(), e);
}
return new CleanupSummary(removedCount, filesDeleted, retainedCount);
}
/** Mirror the in-memory {@code JobResult} into the cluster-visible {@link JobStore}. */
@@ -525,22 +585,43 @@ public class TaskManager {
}
}
/** Clean up files associated with a job result */
private void cleanupJobFiles(JobResult result, String jobId) {
/**
* Clean up files associated with a job result: both the results and the persistent input copy
* an async submit made of the upload.
*
* @return The number of files actually deleted
*/
private int cleanupJobFiles(JobResult result, String jobId) {
int deleted = 0;
// Clean up all result files
if (result.hasFiles()) {
for (ResultFile resultFile : result.getAllResultFiles()) {
try {
fileStorage.deleteFile(resultFile.getFileId());
} catch (Exception e) {
log.warn(
"Failed to delete file {} for job {}: {}",
resultFile.getFileId(),
jobId,
e.getMessage());
if (deleteJobFile(resultFile.getFileId(), jobId)) {
deleted++;
}
}
}
for (String inputFileId : result.getInputFileIds()) {
if (deleteJobFile(inputFileId, jobId)) {
deleted++;
}
}
return deleted;
}
/**
* Deletes as the system, not as the caller: an admin sweeping another user's jobs, or the
* scheduled task running with no security context, is not the file's owner, and the
* ownership-checked delete would throw and leave the file orphaned on disk. The job itself is
* already authorised by the time we get here, and these ids come off that job, not the request.
*/
private boolean deleteJobFile(String fileId, String jobId) {
try {
return fileStorage.deleteFileAsSystem(fileId);
} catch (Exception e) {
log.warn("Failed to delete file {} for job {}: {}", fileId, jobId, e.getMessage());
return false;
}
}
/** Find the ResultFile metadata for a given file ID by searching through all job results */
@@ -140,20 +140,25 @@ public class ChecksumUtils {
for (String algorithm : algorithms) {
String key = algorithm; // keep original key for output
switch (algorithm.toUpperCase(Locale.ROOT)) {
case "CRC32":
checksums.put(key, new CRC32());
break;
case "ADLER32":
checksums.put(key, new Adler32());
break;
default:
try {
// For MessageDigest, pass the original name (case-insensitive per JCA)
digests.put(key, MessageDigest.getInstance(algorithm));
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Unsupported algorithm: " + algorithm, e);
}
Object digestOrChecksum =
switch (algorithm.toUpperCase(Locale.ROOT)) {
case "CRC32" -> new CRC32();
case "ADLER32" -> new Adler32();
default -> {
try {
// For MessageDigest, pass the original name (case-insensitive
// per JCA)
yield MessageDigest.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(
"Unsupported algorithm: " + algorithm, e);
}
}
};
if (digestOrChecksum instanceof Checksum checksum) {
checksums.put(key, checksum);
} else {
digests.put(key, (MessageDigest) digestOrChecksum);
}
}
@@ -751,7 +751,7 @@ public class ExceptionUtils {
String targetDescription;
if (errorInfo.affectedPages() != null && !errorInfo.affectedPages().isEmpty()) {
if (errorInfo.affectedPages().size() == 1) {
targetDescription = "page " + errorInfo.affectedPages().get(0);
targetDescription = "page " + errorInfo.affectedPages().getFirst();
} else {
targetDescription =
"pages "
@@ -848,7 +848,7 @@ public class ExceptionUtils {
}
// Use the first page number, or null if none found
Integer pageNumber = affectedPages.isEmpty() ? null : affectedPages.get(0);
Integer pageNumber = affectedPages.isEmpty() ? null : affectedPages.getFirst();
return new GhostscriptErrorInfo(
ErrorCode.GHOSTSCRIPT_PAGE_DRAWING,
@@ -114,7 +114,7 @@ public enum FormFieldTypeSupport {
return;
}
PDAnnotationWidget widget = checkBox.getWidgets().get(0);
PDAnnotationWidget widget = checkBox.getWidgets().getFirst();
PDAppearanceCharacteristicsDictionary appearanceChars =
widget.getAppearanceCharacteristics();
@@ -88,28 +88,16 @@ public class FormUtils {
* text)
*/
public String detectFieldType(PDField field) {
if (field instanceof PDSignatureField) {
return FIELD_TYPE_SIGNATURE;
}
if (field instanceof PDPushButton) {
return FIELD_TYPE_BUTTON;
}
if (field instanceof PDTextField) {
return FIELD_TYPE_TEXT;
}
if (field instanceof PDCheckBox) {
return FIELD_TYPE_CHECKBOX;
}
if (field instanceof PDComboBox) {
return FIELD_TYPE_COMBOBOX;
}
if (field instanceof PDListBox) {
return FIELD_TYPE_LISTBOX;
}
if (field instanceof PDRadioButton) {
return FIELD_TYPE_RADIO;
}
return FIELD_TYPE_TEXT;
return switch (field) {
case PDSignatureField ignored -> FIELD_TYPE_SIGNATURE;
case PDPushButton ignored -> FIELD_TYPE_BUTTON;
case PDTextField ignored -> FIELD_TYPE_TEXT;
case PDCheckBox ignored -> FIELD_TYPE_CHECKBOX;
case PDComboBox ignored -> FIELD_TYPE_COMBOBOX;
case PDListBox ignored -> FIELD_TYPE_LISTBOX;
case PDRadioButton ignored -> FIELD_TYPE_RADIO;
case null, default -> FIELD_TYPE_TEXT;
};
}
public List<FormFieldInfo> extractFormFields(PDDocument document) {
@@ -583,22 +571,17 @@ public class FormUtils {
continue;
}
String type = info.type();
Object value;
switch (type) {
case FIELD_TYPE_CHECKBOX:
value = isChecked(info.value()) ? Boolean.TRUE : Boolean.FALSE;
break;
case FIELD_TYPE_LISTBOX:
if (info.multiSelect()) {
value = new ArrayList<>();
} else {
value = safeDefault(info.value());
}
break;
case FIELD_TYPE_BUTTON, FIELD_TYPE_SIGNATURE:
continue; // skip non-fillable
default:
value = safeDefault(info.value());
Object value =
switch (type) {
case FIELD_TYPE_CHECKBOX ->
isChecked(info.value()) ? Boolean.TRUE : Boolean.FALSE;
case FIELD_TYPE_LISTBOX ->
info.multiSelect() ? new ArrayList<>() : safeDefault(info.value());
case FIELD_TYPE_BUTTON, FIELD_TYPE_SIGNATURE -> null;
default -> safeDefault(info.value());
};
if (value == null) {
continue; // skip non-fillable
}
record.put(info.name(), value);
}
@@ -949,44 +932,44 @@ public class FormUtils {
if (selection == null || selection.trim().isEmpty()) return null;
List<String> filtered =
filterChoiceSelections(List.of(selection), allowedOptions, fieldName);
return filtered.isEmpty() ? null : filtered.get(0);
return filtered.isEmpty() ? null : filtered.getFirst();
}
private void applyValueToField(PDField field, String value, boolean strict) throws IOException {
try {
if (field instanceof PDTextField textField) {
setTextValue(textField, value);
} else if (field instanceof PDCheckBox checkBox) {
LinkedHashSet<String> candidateStates = collectCheckBoxStates(checkBox);
boolean shouldCheck = shouldCheckBoxBeChecked(value, candidateStates);
try {
if (shouldCheck) {
checkBox.check();
} else {
checkBox.unCheck();
}
} catch (IOException checkProblem) {
log.warn(
"Failed to set checkbox state for '{}': {}",
field.getFullyQualifiedName(),
checkProblem.getMessage(),
checkProblem);
if (strict) {
throw checkProblem;
switch (field) {
case PDTextField textField -> setTextValue(textField, value);
case PDCheckBox checkBox -> {
LinkedHashSet<String> candidateStates = collectCheckBoxStates(checkBox);
boolean shouldCheck = shouldCheckBoxBeChecked(value, candidateStates);
try {
if (shouldCheck) {
checkBox.check();
} else {
checkBox.unCheck();
}
} catch (IOException checkProblem) {
log.warn(
"Failed to set checkbox state for '{}': {}",
field.getFullyQualifiedName(),
checkProblem.getMessage(),
checkProblem);
if (strict) {
throw checkProblem;
}
}
}
} else if (field instanceof PDRadioButton radioButton) {
if (value != null && !value.isBlank()) {
radioButton.setValue(value);
case PDRadioButton radioButton -> {
if (value != null && !value.isBlank()) {
radioButton.setValue(value);
}
}
} else if (field instanceof PDChoice choiceField) {
applyChoiceValue(choiceField, value);
} else if (field instanceof PDPushButton) {
log.debug("Ignore Push button");
} else if (field instanceof PDSignatureField) {
log.debug("Skipping signature field '{}'", field.getFullyQualifiedName());
} else {
field.setValue(value != null ? value : "");
case PDChoice choiceField -> applyChoiceValue(choiceField, value);
case PDPushButton ignored -> log.debug("Ignore Push button");
case PDSignatureField ignored ->
log.debug("Skipping signature field '{}'", field.getFullyQualifiedName());
case null -> log.warn("Attempted to set value on null field");
default -> field.setValue(value != null ? value : "");
}
} catch (Exception e) {
log.warn(
@@ -1306,37 +1289,42 @@ public class FormUtils {
List<String> resolveOptions(PDTerminalField field) {
try {
if (field instanceof PDChoice choice) {
LinkedHashSet<String> allowed = new LinkedHashSet<>();
List<String> exportValues = choice.getOptionsExportValues();
List<String> displayValues = choice.getOptionsDisplayValues();
return switch (field) {
case PDChoice choice -> {
LinkedHashSet<String> allowed = new LinkedHashSet<>();
List<String> exportValues = choice.getOptionsExportValues();
List<String> displayValues = choice.getOptionsDisplayValues();
if (exportValues != null) {
exportValues.stream()
.filter(Objects::nonNull)
.map(String::trim)
.filter(s -> !s.isEmpty())
.forEach(allowed::add);
if (exportValues != null) {
exportValues.stream()
.filter(Objects::nonNull)
.map(String::trim)
.filter(s -> !s.isEmpty())
.forEach(allowed::add);
}
if (displayValues != null) {
displayValues.stream()
.filter(Objects::nonNull)
.map(String::trim)
.filter(s -> !s.isEmpty())
.forEach(allowed::add);
}
yield new ArrayList<>(allowed);
}
if (displayValues != null) {
displayValues.stream()
.filter(Objects::nonNull)
.map(String::trim)
.filter(s -> !s.isEmpty())
.forEach(allowed::add);
case PDRadioButton radio -> {
List<String> exports = radio.getExportValues();
yield exports != null && !exports.isEmpty()
? new ArrayList<>(exports)
: Collections.emptyList();
}
return new ArrayList<>(allowed);
} else if (field instanceof PDRadioButton radio) {
List<String> exports = radio.getExportValues();
if (exports != null && !exports.isEmpty()) {
return new ArrayList<>(exports);
case PDCheckBox checkBox -> {
List<String> exports = checkBox.getExportValues();
yield exports != null && !exports.isEmpty()
? new ArrayList<>(exports)
: Collections.emptyList();
}
} else if (field instanceof PDCheckBox checkBox) {
List<String> exports = checkBox.getExportValues();
if (exports != null && !exports.isEmpty()) {
return new ArrayList<>(exports);
}
}
case null, default -> Collections.emptyList();
};
} catch (Exception e) {
log.debug(
"Failed to resolve options for field '{}': {}",
@@ -1465,7 +1453,7 @@ public class FormUtils {
// Only check options for choice-type fields (combobox, listbox, radio)
if (CHOICE_FIELD_TYPES.contains(type) && options != null && !options.isEmpty()) {
String optionCandidate = cleanLabel(options.get(0));
String optionCandidate = cleanLabel(options.getFirst());
if (optionCandidate != null && !looksGeneric(optionCandidate)) {
return optionCandidate;
}
@@ -1557,7 +1545,7 @@ public class FormUtils {
continue;
}
PDAnnotationWidget widget = widgets.get(0);
PDAnnotationWidget widget = widgets.getFirst();
PDRectangle originalRectangle = cloneRectangle(widget.getRectangle());
PDPage page = resolveWidgetPage(document, widget, null);
if (page == null || originalRectangle == null) {
@@ -2446,19 +2434,19 @@ public class FormUtils {
private static int firstWidgetPageIndex(FormFieldWithCoordinates f) {
return (f.getWidgets() != null && !f.getWidgets().isEmpty())
? f.getWidgets().get(0).getPageIndex()
? f.getWidgets().getFirst().getPageIndex()
: -1;
}
private static float firstWidgetY(FormFieldWithCoordinates f) {
return (f.getWidgets() != null && !f.getWidgets().isEmpty())
? f.getWidgets().get(0).getY()
? f.getWidgets().getFirst().getY()
: 0;
}
private static float firstWidgetX(FormFieldWithCoordinates f) {
return (f.getWidgets() != null && !f.getWidgets().isEmpty())
? f.getWidgets().get(0).getX()
? f.getWidgets().getFirst().getX()
: 0;
}
@@ -12,6 +12,7 @@ import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.MessageDigest;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -54,6 +55,10 @@ public class GeneralUtils {
private final String DEFAULT_WEBUI_CONFIGS_DIR = "defaultWebUIConfigs";
private final String PYTHON_SCRIPTS_DIR = "python";
// Extracted once per run. Rewriting a script while another request is exec-ing it
// races wherever rename is not atomic, such as 9p or NFS bind mounts.
private final Map<String, Path> EXTRACTED_SCRIPTS = new ConcurrentHashMap<>();
private final RegexPatternUtils patternCache = RegexPatternUtils.getInstance();
// Valid size units used for convertSizeToBytes validation and parsing
private final Set<String> VALID_SIZE_UNITS = Set.of("B", "KB", "MB", "GB", "TB");
@@ -1025,17 +1030,30 @@ public class GeneralUtils {
}
Path scriptsDir = Path.of(InstallationPathConfig.getScriptsPath(), PYTHON_SCRIPTS_DIR);
Files.createDirectories(scriptsDir);
Path target = scriptsDir.resolve(scriptName);
ClassPathResource res =
new ClassPathResource("static/" + PYTHON_SCRIPTS_DIR + "/" + scriptName);
if (!res.exists()) {
log.error("Resource not found: {}", res.getPath());
throw new IOException("Resource not found: " + res.getPath());
Path cached = EXTRACTED_SCRIPTS.get(scriptName);
if (cached != null && Files.isRegularFile(cached)) {
return cached;
}
synchronized (EXTRACTED_SCRIPTS) {
cached = EXTRACTED_SCRIPTS.get(scriptName);
if (cached != null && Files.isRegularFile(cached)) {
return cached;
}
Files.createDirectories(scriptsDir);
ClassPathResource res =
new ClassPathResource("static/" + PYTHON_SCRIPTS_DIR + "/" + scriptName);
if (!res.exists()) {
log.error("Resource not found: {}", res.getPath());
throw new IOException("Resource not found: " + res.getPath());
}
copyResourceToFile(res, target);
EXTRACTED_SCRIPTS.put(scriptName, target);
return target;
}
copyResourceToFile(res, target);
return target;
}
/*
@@ -26,29 +26,27 @@ import lombok.extern.slf4j.Slf4j;
public class ImageProcessingUtils {
static BufferedImage convertColorType(BufferedImage sourceImage, String colorType) {
BufferedImage convertedImage;
switch (colorType) {
case "greyscale":
convertedImage =
return switch (colorType) {
case "greyscale" -> {
BufferedImage convertedImage =
new BufferedImage(
sourceImage.getWidth(),
sourceImage.getHeight(),
BufferedImage.TYPE_BYTE_GRAY);
convertedImage.getGraphics().drawImage(sourceImage, 0, 0, null);
break;
case "blackwhite":
convertedImage =
yield convertedImage;
}
case "blackwhite" -> {
BufferedImage convertedImage =
new BufferedImage(
sourceImage.getWidth(),
sourceImage.getHeight(),
BufferedImage.TYPE_BYTE_BINARY);
convertedImage.getGraphics().drawImage(sourceImage, 0, 0, null);
break;
default: // full color
convertedImage = sourceImage;
break;
}
return convertedImage;
yield convertedImage;
}
default -> sourceImage;
};
}
public static byte[] getImageData(BufferedImage image) {
@@ -330,7 +330,7 @@ public class PDFToFile {
if (outputFiles.size() == 1) {
// Return single output file
File outputFile = outputFiles.get(0);
File outputFile = outputFiles.getFirst();
if ("txt:Text".equals(outputFormat)) {
outputFormat = "txt";
}
@@ -307,7 +307,7 @@ public class ProcessExecutor {
boolean isQpdf =
commandToRun != null
&& !commandToRun.isEmpty()
&& commandToRun.get(0).contains("qpdf");
&& commandToRun.getFirst().contains("qpdf");
if (!outputLines.isEmpty()) {
String outputMessage = String.join("\n", outputLines);
@@ -370,7 +370,7 @@ public class ProcessExecutor {
}
// Check if this is a UNO conversion by looking for unoconvert executable
String executable = command.get(0);
String executable = command.getFirst();
if (executable != null) {
// Extract basename from path for matching
String basename = executable;
@@ -504,7 +504,7 @@ public class ProcessExecutor {
}
// Validate executable (first argument)
String executable = command.get(0);
String executable = command.getFirst();
if (executable == null || executable.isBlank()) {
throw new IllegalArgumentException("Command executable must not be empty");
}
@@ -56,8 +56,10 @@ public class RequestUriUtils {
return true;
}
// Mobile scanner page for QR code-based file uploads (peer-to-peer, no backend auth needed)
if (normalizedUri.startsWith("/mobile-scanner")) {
// Mobile pages reached by scanning a QR code (peer-to-peer, no backend auth
// needed): /mobile-scanner uploads photos, /mobile-sign draws a signature.
if (normalizedUri.startsWith("/mobile-scanner")
|| normalizedUri.startsWith("/mobile-sign")) {
return true;
}
@@ -114,7 +114,7 @@ public class YamlHelper {
for (NodeTuple tuple : mappingNode.getValue()) {
ScalarNode keyNode = (tuple.getKeyNode() instanceof ScalarNode sk) ? sk : null;
if (keyNode == null || !keyNode.getValue().equals(keys.get(0))) {
if (keyNode == null || !keyNode.getValue().equals(keys.getFirst())) {
updatedTuples.add(tuple);
continue;
}
@@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test;
import stirling.software.SPDF.config.EndpointConfiguration.DisableReason;
import stirling.software.SPDF.config.EndpointConfiguration.EndpointAvailability;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.service.PdfaLevelAServiceInterface;
/**
* Unit tests for {@link EndpointConfiguration}. The class wires up its endpoint/group registry in
@@ -32,7 +33,14 @@ class EndpointConfigurationGapTest {
* Construct an EndpointConfiguration with the given pro flag and current applicationProperties.
*/
private EndpointConfiguration build(boolean runningProOrHigher) {
return new EndpointConfiguration(applicationProperties, runningProOrHigher);
return build(runningProOrHigher, null);
}
/** The PDF/UA service is only present in proprietary builds, so it is injected separately. */
private EndpointConfiguration build(
boolean runningProOrHigher, PdfaLevelAServiceInterface pdfaLevelAService) {
return new EndpointConfiguration(
applicationProperties, runningProOrHigher, pdfaLevelAService);
}
/** Default config: not pro, no removals, url-to-pdf disabled (default System flag is false). */
@@ -177,6 +185,28 @@ class EndpointConfigurationGapTest {
}
}
@Nested
@DisplayName("PDF/UA availability")
class PdfUaTests {
@Test
@DisplayName("the PDF/UA endpoints are off when the proprietary tagger is absent")
void disabledWithoutTagger() {
EndpointConfiguration config = build(false, null);
assertFalse(config.isEndpointEnabled("pdf-to-ua"));
assertFalse(config.isEndpointEnabled("accessibility-report"));
}
@Test
@DisplayName("they are on once the tagger is on the classpath")
void enabledWithTagger() {
EndpointConfiguration config =
build(false, (pdfBytes, part, language, title, alsoDeclareUa) -> null);
assertTrue(config.isEndpointEnabled("pdf-to-ua"));
assertTrue(config.isEndpointEnabled("accessibility-report"));
}
}
@Nested
@DisplayName("group enable / disable")
class GroupTests {
@@ -2,6 +2,7 @@ package stirling.software.common.service;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -65,6 +66,24 @@ class FileStorageOwnershipTest {
assertThrows(SecurityException.class, () -> fs.deleteFile(id));
}
@Test
void systemDeleteOfAnotherUsersFile_allowed_soJobCleanupDoesNotOrphanIt(@TempDir Path tempDir)
throws IOException {
// An admin sweeping every user's finished jobs is not the owner of their files. The
// ownership-checked delete throws there, which used to drop the job record and leave the
// files stranded on disk with nothing left able to reference them.
AtomicReference<String> user = new AtomicReference<>("alice");
FileStorage fs = newStorageWithCurrentUser(tempDir, user);
String id = fs.storeBytes("alice's file".getBytes(), "x.bin");
user.set("admin");
assertThrows(SecurityException.class, () -> fs.deleteFile(id));
assertTrue(fs.deleteFileAsSystem(id), "System delete must not be blocked by ownership");
user.set("alice");
assertThrows(IOException.class, () -> fs.retrieveBytes(id), "File should really be gone");
}
@Test
void anonymousRetrieveOfOwnedFile_allowed_noCurrentUserMeansNoCompare(@TempDir Path tempDir)
throws IOException {
@@ -0,0 +1,263 @@
package stirling.software.common.service;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.http.MediaType;
import org.springframework.test.util.ReflectionTestUtils;
import stirling.software.common.cluster.ClusterBackplane;
import stirling.software.common.cluster.JobStore;
import stirling.software.common.model.job.JobResult;
import stirling.software.common.model.job.ResultFile;
/**
* Covers the on-demand cleanup path and the input-copy tracking that makes it complete. An async
* submit persists a copy of the upload as well as its results; before both were tracked, only the
* results were ever deleted and the input copy stayed on disk indefinitely.
*/
class TaskManagerCleanupTest {
@Mock private FileStorage fileStorage;
@Mock private JobStore jobStore;
@Mock private ClusterBackplane clusterBackplane;
@InjectMocks private TaskManager taskManager;
private AutoCloseable closeable;
@BeforeEach
void setUp() {
closeable = MockitoAnnotations.openMocks(this);
lenient().when(clusterBackplane.localNodeId()).thenReturn("test-node");
lenient().when(clusterBackplane.shouldRunLocalCleanup()).thenReturn(true);
lenient().when(fileStorage.deleteFileAsSystem(anyString())).thenReturn(true);
ReflectionTestUtils.setField(taskManager, "jobResultExpiryMinutes", 30);
ReflectionTestUtils.setField(taskManager, "pendingJobExpiryMinutes", 1440);
}
@AfterEach
void tearDown() throws Exception {
closeable.close();
}
@SuppressWarnings("unchecked")
private Map<String, JobResult> jobResults() {
return (Map<String, JobResult>) ReflectionTestUtils.getField(taskManager, "jobResults");
}
/** Complete a job with a single result file, as an async file-producing job would. */
private void completeWithFile(String jobId, String fileId) {
taskManager.setFileResult(jobId, fileId, "out.pdf", MediaType.APPLICATION_PDF_VALUE);
taskManager.setComplete(jobId);
}
@Test
void forcedCleanupRemovesFinishedJobsRegardlessOfAge() {
String jobId = "fresh-job";
taskManager.createTask(jobId);
completeWithFile(jobId, "result-file");
// The scheduled sweep leaves it alone: it completed well inside the retention window.
taskManager.cleanupOldJobs();
assertTrue(jobResults().containsKey(jobId), "Scheduled cleanup should respect the expiry");
TaskManager.CleanupSummary summary = taskManager.cleanupFinishedJobsNow(id -> true);
assertEquals(1, summary.jobsRemoved());
assertEquals(1, summary.filesDeleted());
assertEquals(0, summary.jobsRetained());
assertFalse(jobResults().containsKey(jobId));
verify(fileStorage).deleteFileAsSystem("result-file");
verify(jobStore).delete(jobId);
}
@Test
void forcedCleanupDeletesThePersistedInputCopy() {
String jobId = "job-with-input";
taskManager.createTask(jobId);
assertTrue(taskManager.registerInputFile(jobId, "input-file"));
completeWithFile(jobId, "result-file");
TaskManager.CleanupSummary summary = taskManager.cleanupFinishedJobsNow(id -> true);
assertEquals(1, summary.jobsRemoved());
assertEquals(2, summary.filesDeleted(), "Both the result and the input copy must go");
verify(fileStorage).deleteFileAsSystem("result-file");
verify(fileStorage).deleteFileAsSystem("input-file");
}
@Test
void scheduledCleanupAlsoDeletesThePersistedInputCopy() {
String jobId = "expired-job";
taskManager.createTask(jobId);
taskManager.registerInputFile(jobId, "input-file");
completeWithFile(jobId, "result-file");
JobResult result = taskManager.getJobResult(jobId);
ReflectionTestUtils.setField(result, "completedAt", LocalDateTime.now().minusHours(1));
taskManager.cleanupOldJobs();
assertFalse(jobResults().containsKey(jobId));
verify(fileStorage).deleteFileAsSystem("result-file");
verify(fileStorage).deleteFileAsSystem("input-file");
}
@Test
void forcedCleanupLeavesRunningJobsAlone() {
String running = "running-job";
taskManager.createTask(running);
taskManager.registerInputFile(running, "in-flight-input");
TaskManager.CleanupSummary summary = taskManager.cleanupFinishedJobsNow(id -> true);
assertEquals(0, summary.jobsRemoved());
assertEquals(0, summary.filesDeleted());
assertEquals(1, summary.jobsRetained());
assertTrue(jobResults().containsKey(running));
// Deleting a running job's input mid-flight would break it.
verify(fileStorage, never()).deleteFileAsSystem(anyString());
}
@Test
void forcedCleanupSkipsJobsTheFilterRejects() {
taskManager.createTask("alice:job");
completeWithFile("alice:job", "alice-file");
taskManager.createTask("bob:job");
completeWithFile("bob:job", "bob-file");
TaskManager.CleanupSummary summary =
taskManager.cleanupFinishedJobsNow(id -> id.startsWith("alice:"));
assertEquals(1, summary.jobsRemoved());
assertEquals(1, summary.jobsRetained());
assertFalse(jobResults().containsKey("alice:job"));
assertTrue(jobResults().containsKey("bob:job"), "Another user's job must survive");
verify(fileStorage).deleteFileAsSystem("alice-file");
verify(fileStorage, never()).deleteFileAsSystem("bob-file");
}
@Test
void forcedCleanupIsIdempotent() {
String jobId = "job-to-clean";
taskManager.createTask(jobId);
taskManager.registerInputFile(jobId, "input-file");
completeWithFile(jobId, "result-file");
taskManager.cleanupFinishedJobsNow(id -> true);
TaskManager.CleanupSummary second = taskManager.cleanupFinishedJobsNow(id -> true);
assertEquals(0, second.jobsRemoved());
assertEquals(0, second.filesDeleted());
}
@Test
void forcedCleanupRunsEvenWhenTheBackplaneOwnsScheduledExpiry() {
// The scheduled sweep defers to the backplane TTL in cluster mode, but an explicit
// request to release this node's storage still has to do something.
when(clusterBackplane.shouldRunLocalCleanup()).thenReturn(false);
String jobId = "clustered-job";
taskManager.createTask(jobId);
completeWithFile(jobId, "result-file");
taskManager.cleanupOldJobs();
assertTrue(jobResults().containsKey(jobId));
TaskManager.CleanupSummary summary = taskManager.cleanupFinishedJobsNow(id -> true);
assertEquals(1, summary.jobsRemoved());
assertFalse(jobResults().containsKey(jobId));
}
@Test
void cleanupCountsOnlyFilesThatWereActuallyDeleted() {
// A file already gone (a retry deleted it, say) must not be counted as freed.
String jobId = "partially-cleaned";
taskManager.createTask(jobId);
taskManager.registerInputFile(jobId, "already-gone");
completeWithFile(jobId, "result-file");
when(fileStorage.deleteFileAsSystem("already-gone")).thenReturn(false);
TaskManager.CleanupSummary summary = taskManager.cleanupFinishedJobsNow(id -> true);
assertEquals(1, summary.filesDeleted());
}
@Test
void cleanupSurvivesAFileStorageFailure() {
String jobId = "job-with-unhappy-storage";
taskManager.createTask(jobId);
taskManager.registerInputFile(jobId, "input-file");
completeWithFile(jobId, "result-file");
when(fileStorage.deleteFileAsSystem("result-file"))
.thenThrow(new RuntimeException("disk on fire"));
TaskManager.CleanupSummary summary = taskManager.cleanupFinishedJobsNow(id -> true);
// The job is still released and the remaining file still deleted.
assertEquals(1, summary.jobsRemoved());
assertEquals(1, summary.filesDeleted());
assertFalse(jobResults().containsKey(jobId));
verify(fileStorage).deleteFileAsSystem("input-file");
}
@Test
void registerInputFileRejectsAnUnknownJob() {
assertFalse(taskManager.registerInputFile("no-such-job", "input-file"));
}
@Test
void registerInputFileIgnoresDuplicatesAndBlanks() {
String jobId = "dedupe-job";
taskManager.createTask(jobId);
taskManager.registerInputFile(jobId, "input-file");
taskManager.registerInputFile(jobId, "input-file");
taskManager.registerInputFile(jobId, " ");
taskManager.registerInputFile(jobId, null);
List<String> inputFileIds = taskManager.getJobResult(jobId).getInputFileIds();
assertEquals(List.of("input-file"), inputFileIds);
}
@Test
void multiFileResultsAndTheInputCopyAreAllDeleted() {
String jobId = "split-job";
taskManager.createTask(jobId);
taskManager.registerInputFile(jobId, "input-file");
JobResult result = taskManager.getJobResult(jobId);
result.completeWithFiles(
List.of(
ResultFile.builder()
.fileId("page-1")
.fileName("1.pdf")
.contentType(MediaType.APPLICATION_PDF_VALUE)
.fileSize(10L)
.build(),
ResultFile.builder()
.fileId("page-2")
.fileName("2.pdf")
.contentType(MediaType.APPLICATION_PDF_VALUE)
.fileSize(10L)
.build()));
TaskManager.CleanupSummary summary = taskManager.cleanupFinishedJobsNow(id -> true);
assertEquals(3, summary.filesDeleted());
verify(fileStorage).deleteFileAsSystem("page-1");
verify(fileStorage).deleteFileAsSystem("page-2");
verify(fileStorage).deleteFileAsSystem("input-file");
}
}
@@ -290,7 +290,8 @@ class TaskManagerMoreTest {
ReflectionTestUtils.setField(job, "complete", true);
ReflectionTestUtils.setField(job, "completedAt", LocalDateTime.now().minusHours(2));
when(fileStorage.deleteFile("doomed")).thenThrow(new RuntimeException("locked"));
when(fileStorage.deleteFileAsSystem("doomed"))
.thenThrow(new RuntimeException("locked"));
// Must not propagate; the job is still removed afterwards.
taskManager.cleanupOldJobs();
@@ -258,7 +258,7 @@ class TaskManagerTest {
.build();
ReflectionTestUtils.setField(oldJob, "resultFiles", java.util.List.of(resultFile));
when(fileStorage.deleteFile("file-id")).thenReturn(true);
when(fileStorage.deleteFileAsSystem("file-id")).thenReturn(true);
// Obtain access to the private jobResults map
Map<String, JobResult> jobResultsMap =
@@ -281,7 +281,7 @@ class TaskManagerTest {
assertFalse(jobResultsMap.containsKey(oldJobId));
assertTrue(jobResultsMap.containsKey(recentJobId));
assertTrue(jobResultsMap.containsKey(activeJobId));
verify(fileStorage).deleteFile("file-id");
verify(fileStorage).deleteFileAsSystem("file-id");
}
@Test
@@ -308,7 +308,7 @@ class TaskManagerTest {
// Assert: nothing was removed locally, and no jobStore.delete was issued.
assertTrue(jobResultsMap.containsKey(oldJobId));
verify(jobStore, never()).delete(anyString());
verify(fileStorage, never()).deleteFile(anyString());
verify(fileStorage, never()).deleteFileAsSystem(anyString());
}
@Test
@@ -721,7 +721,7 @@ class PDFToFileTest {
.thenAnswer(
invocation -> {
List<String> args = invocation.getArgument(0);
String outputPath = args.get(args.size() - 1);
String outputPath = args.getLast();
Files.write(Path.of(outputPath), "Fake DOCX content".getBytes());
return mockExecutorResult;
});
@@ -73,6 +73,13 @@ class RequestUriUtilsTest {
assertTrue(RequestUriUtils.isStaticResource("/mobile-scanner"));
}
@Test
void testIsStaticResource_mobileSignPath() {
// The phone-side signature drawing page, reached from the Sign tool QR code.
assertTrue(RequestUriUtils.isStaticResource("/mobile-sign"));
assertTrue(RequestUriUtils.isStaticResource("/app", "/app/mobile-sign"));
}
@Test
void testIsStaticResource_portalShell() {
// The admin portal SPA shell (/processor) is served pre-auth so it's directly navigable.
+20 -2
View File
@@ -312,8 +312,9 @@ tasks.register('cleanFrontendAssets', Delete) {
delete generatedFrontendPaths.collect { new File(resourcesStaticDir, it) }
// Prerendered per-route SPA pages (e.g. compress.html) carry per-tool OG tags and are
// copied from the frontend build. Remove stale ones so renamed/removed tools don't linger.
// api-landing.html and mobile-upload.html are real backend source files, not generated artifacts.
delete fileTree(dir: resourcesStaticDir, includes: ['*.html'], excludes: ['api-landing.html', 'mobile-upload.html'])
// api-landing.html, mobile-upload.html and mobile-sign.html are real backend source files,
// not generated artifacts.
delete fileTree(dir: resourcesStaticDir, includes: ['*.html'], excludes: ['api-landing.html', 'mobile-upload.html', 'mobile-sign.html'])
// Nested prerendered route pages (e.g. settings/people.html)
delete new File(resourcesStaticDir, 'settings')
}
@@ -330,11 +331,27 @@ tasks.register('copyApiLandingPage', Copy) {
}
}
tasks.register('copyBackendOnlySamples', Copy) {
group = 'frontend'
description = 'Copy frontend sample files for backend-only mode'
from(new File(frontendEditorDir, 'public/samples'))
into(new File(resourcesStaticDir, 'samples'))
dependsOn cleanFrontendAssets
onlyIf { !buildWithFrontend }
}
// Ensure copyFrontendAssets runs after spotless tasks
tasks.named('copyFrontendAssets').configure {
mustRunAfter tasks.matching { it.name.startsWith('spotless') }
}
// Cleanup removes frontend-generated resources that are also visible to the resource/formatting
// task graph. Keep all Spotless tasks ahead of cleanup so they never snapshot a path that has
// just been removed.
tasks.named('cleanFrontendAssets').configure {
mustRunAfter tasks.matching { it.name.startsWith('spotless') }
}
if (buildWithFrontend) {
println "Editor frontend build enabled - JAR will include React frontend (mode=${frontendMode})"
processResources.dependsOn copyFrontendAssets
@@ -342,6 +359,7 @@ if (buildWithFrontend) {
println "Frontend build disabled - JAR will be backend-only with API landing page"
// When not building the UI, ensure any stale frontend assets are removed and use API landing page
processResources.dependsOn copyApiLandingPage
processResources.dependsOn copyBackendOnlySamples
}
bootJar.dependsOn ':common:jar'
@@ -1,6 +1,5 @@
package stirling.software.SPDF.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -22,7 +21,11 @@ import stirling.software.SPDF.controller.web.UploadLimitService;
@Slf4j
public class MultipartConfiguration {
@Autowired private UploadLimitService uploadLimitService;
private final UploadLimitService uploadLimitService;
public MultipartConfiguration(UploadLimitService uploadLimitService) {
this.uploadLimitService = uploadLimitService;
}
/**
* Creates MultipartConfigElement that respects fileUploadLimit from settings.yml or environment
@@ -297,24 +297,25 @@ public class BookletImpositionController {
// Apply rotation if needed (rotate about origin), then translate to keep in cell
switch (rot) {
case 90:
case 90 -> {
cs.transform(Matrix.getRotateInstance(Math.PI / 2, 0, 0));
// After 90° CCW, the content spans x in [-r.getHeight(), 0] and y in [0,
// r.getWidth()]
cs.transform(Matrix.getTranslateInstance(0, -r.getWidth()));
break;
case 180:
}
case 180 -> {
cs.transform(Matrix.getRotateInstance(Math.PI, 0, 0));
cs.transform(Matrix.getTranslateInstance(-r.getWidth(), -r.getHeight()));
break;
case 270:
}
case 270 -> {
cs.transform(Matrix.getRotateInstance(3 * Math.PI / 2, 0, 0));
// After 270° CCW, the content spans x in [0, r.getHeight()] and y in
// [-r.getWidth(), 0]
cs.transform(Matrix.getTranslateInstance(-r.getHeight(), 0));
break;
default:
}
default -> {
// 0°: no-op
}
}
// Reuse LayerUtility passed from caller
@@ -9,7 +9,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
@@ -61,8 +60,6 @@ import stirling.software.jpdfium.doc.PdfBookmarkEditor.BookmarkTree;
@Slf4j
@RequiredArgsConstructor
public class MergeController {
private static final Pattern QUOTE_WRAP_PATTERN = Pattern.compile("^\"|\"$");
private final CustomPDFDocumentFactory pdfDocumentFactory;
private final TempFileManager tempFileManager;
@@ -164,30 +161,6 @@ public class MergeController {
};
}
private String[] parseClientFileIds(String clientFileIds) {
if (clientFileIds == null || clientFileIds.trim().isEmpty()) {
return new String[0];
}
try {
String trimmed = clientFileIds.trim();
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
String inside = trimmed.substring(1, trimmed.length() - 1).trim();
if (inside.isEmpty()) {
return new String[0];
}
String[] parts = inside.split(",");
String[] result = new String[parts.length];
for (int i = 0; i < parts.length; i++) {
result[i] = QUOTE_WRAP_PATTERN.matcher(parts[i].trim()).replaceAll("");
}
return result;
}
} catch (Exception e) {
log.warn("Failed to parse client file IDs: {}", clientFileIds, e);
}
return new String[0];
}
private void addTableOfContents(PDDocument mergedDocument, MultipartFile[] files) {
PDDocumentOutline outline = new PDDocumentOutline();
mergedDocument.getDocumentCatalog().setDocumentOutline(outline);
@@ -125,17 +125,14 @@ public class UIDataController {
pipelineConfigs.add(content);
}
for (String config : pipelineConfigs) {
for (int i = 0; i < jsonFiles.size(); i++) {
String config = pipelineConfigs.get(i);
Map<String, Object> jsonContent =
objectMapper.readValue(
config, new TypeReference<Map<String, Object>>() {});
String name = (String) jsonContent.get("name");
if (name == null || name.isEmpty()) {
String filename =
jsonFiles
.get(pipelineConfigs.indexOf(config))
.getFileName()
.toString();
String filename = jsonFiles.get(i).getFileName().toString();
name = filename.substring(0, filename.lastIndexOf('.'));
}
Map<String, String> configWithName = new HashMap<>();
@@ -301,20 +298,14 @@ public class UIDataController {
}
private static String getFormatFromExtension(String extension) {
switch (extension) {
case "ttf":
return "truetype";
case "woff":
return "woff";
case "woff2":
return "woff2";
case "eot":
return "embedded-opentype";
case "svg":
return "svg";
default:
return "";
}
return switch (extension) {
case "ttf" -> "truetype";
case "woff" -> "woff";
case "woff2" -> "woff2";
case "eot" -> "embedded-opentype";
case "svg" -> "svg";
default -> "";
};
}
}
}
@@ -200,7 +200,7 @@ public class ConvertImgPDFController {
}
if (webpFiles.size() == 1) {
Path webpFilePath = webpFiles.get(0);
Path webpFilePath = webpFiles.getFirst();
byte[] webpBytes = Files.readAllBytes(webpFilePath);
Files.deleteIfExists(tempFile);
tempFile = null;
@@ -11,6 +11,7 @@ import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.Locale;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -71,6 +72,7 @@ import org.apache.xmpbox.schema.PDFAIdentificationSchema;
import org.apache.xmpbox.schema.XMPBasicSchema;
import org.apache.xmpbox.xml.DomXmpParser;
import org.apache.xmpbox.xml.XmpSerializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@@ -83,7 +85,6 @@ import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.converters.PdfToPdfARequest;
@@ -93,6 +94,7 @@ import stirling.software.common.configuration.RuntimePathConfig;
import stirling.software.common.enumeration.ResourceWeight;
import stirling.software.common.model.tool.ToolFormat;
import stirling.software.common.model.tool.ToolIO;
import stirling.software.common.service.PdfaLevelAServiceInterface;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.ProcessExecutor;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
@@ -102,14 +104,26 @@ import stirling.software.common.util.WebResponseUtils;
@ConvertApi
@Slf4j
@RequiredArgsConstructor
public class ConvertPDFToPDFA {
private static final Pattern NON_PRINTABLE_ASCII = Pattern.compile("[^\\x20-\\x7E]");
private final RuntimePathConfig runtimePathConfig;
private final stirling.software.SPDF.service.VeraPDFService veraPDFService;
// Level A needs the proprietary tagger; core builds convert at level B instead.
private final PdfaLevelAServiceInterface pdfaLevelAService;
private final TempFileManager tempFileManager;
public ConvertPDFToPDFA(
RuntimePathConfig runtimePathConfig,
stirling.software.SPDF.service.VeraPDFService veraPDFService,
@Autowired(required = false) PdfaLevelAServiceInterface pdfaLevelAService,
TempFileManager tempFileManager) {
this.runtimePathConfig = runtimePathConfig;
this.veraPDFService = veraPDFService;
this.pdfaLevelAService = pdfaLevelAService;
this.tempFileManager = tempFileManager;
}
private static final String ICC_RESOURCE_PATH = "/icc/sRGB2014.icc";
private static final int PDFA_COMPATIBILITY_POLICY = 1;
@@ -604,7 +618,10 @@ public class ConvertPDFToPDFA {
return handlePdfXConversion(inputFile, outputFormat);
} else {
return handlePdfAConversion(
inputFile, outputFormat, request.getStrict() != null && request.getStrict());
inputFile,
outputFormat,
request.getStrict() != null && request.getStrict(),
request.getPdfUa() != null && request.getPdfUa());
}
}
@@ -1815,8 +1832,64 @@ public class ConvertPDFToPDFA {
return Files.readAllBytes(outputPdf);
}
/** Tags a converted PDF/A for level A; must run after Ghostscript, which discards tags. */
private PdfaLevelAServiceInterface.Result applyLevelA(
byte[] converted,
Path original,
PdfaProfile profile,
String baseFileName,
boolean declarePdfUa) {
if (!profile.requiresTagging()) {
return new PdfaLevelAServiceInterface.Result(converted, true, List.of());
}
if (pdfaLevelAService == null) {
return new PdfaLevelAServiceInterface.Result(
converted,
false,
List.of(
"Level A tagging is not available in this build, so the file was left"
+ " at conformance level B."));
}
// Prefer the document's own title/language; hardcoding "en" mislabelled German reports.
// Read the original, not the converted bytes: Ghostscript discards /Lang, so probing its
// output always yields null and every document would be relabelled with the default.
String language = null;
String title = null;
try (PDDocument probe = Loader.loadPDF(original.toFile())) {
language = probe.getDocumentCatalog().getLanguage();
title = probe.getDocumentInformation().getTitle();
} catch (IOException e) {
log.debug("Could not read original title/language: {}", e.getMessage());
}
if (language == null || language.isBlank()) {
try (PDDocument probe = Loader.loadPDF(converted)) {
language = probe.getDocumentCatalog().getLanguage();
if (title == null || title.isBlank()) {
title = probe.getDocumentInformation().getTitle();
}
} catch (IOException e) {
log.debug("Could not read converted title/language: {}", e.getMessage());
}
}
PdfaLevelAServiceInterface.Result result =
pdfaLevelAService.upgradeToLevelA(
converted,
profile.getPart(),
language,
title != null && !title.isBlank() ? title : baseFileName,
declarePdfUa);
result.warnings().forEach(warning -> log.info("PDF/A level A: {}", warning));
if (!result.levelA()) {
log.warn(
"{} requested but the document could not be tagged; returning level B",
profile.getDisplayName());
}
return result;
}
private ResponseEntity<Resource> handlePdfAConversion(
MultipartFile inputFile, String outputFormat, boolean strict) throws Exception {
MultipartFile inputFile, String outputFormat, boolean strict, boolean declarePdfUa)
throws Exception {
PdfaProfile profile = PdfaProfile.fromRequest(outputFormat);
// Get the original filename without extension
@@ -1841,12 +1914,15 @@ public class ConvertPDFToPDFA {
log.info("Using Ghostscript for PDF/A conversion to {}", profile.getDisplayName());
try {
converted = convertWithGhostscript(inputPath, workingDir, profile);
String outputFilename = baseFileName + profile.outputSuffix();
var levelA =
applyLevelA(converted, inputPath, profile, baseFileName, declarePdfUa);
converted = levelA.pdfBytes();
String outputFilename = baseFileName + profile.outputSuffix(levelA.levelA());
validateAndWarnPdfA(converted, profile, "Ghostscript");
if (strict) {
verifyStrictCompliance(converted);
verifyStrictCompliance(converted, profile, levelA.levelA());
}
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
@@ -1867,13 +1943,15 @@ public class ConvertPDFToPDFA {
}
converted = convertWithPdfBoxMethod(inputPath, profile);
String outputFilename = baseFileName + profile.outputSuffix();
var levelA = applyLevelA(converted, inputPath, profile, baseFileName, declarePdfUa);
converted = levelA.pdfBytes();
String outputFilename = baseFileName + profile.outputSuffix(levelA.levelA());
// Validate with PDFBox preflight and warn if issues found
validateAndWarnPdfA(converted, profile, "PDFBox/LibreOffice");
if (strict) {
verifyStrictCompliance(converted);
verifyStrictCompliance(converted, profile, levelA.levelA());
}
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
@@ -1889,11 +1967,56 @@ public class ConvertPDFToPDFA {
}
}
private void verifyStrictCompliance(byte[] pdfBytes) throws IOException {
/** True for a PDF/UA or WCAG result, which says nothing about archival conformance. */
private static boolean isAccessibilityProfile(
stirling.software.SPDF.model.api.security.PDFVerificationResult result) {
String profile = result.getValidationProfile();
if (profile == null) {
return false;
}
String normalised = profile.toLowerCase(Locale.ROOT);
return normalised.contains("ua") || normalised.contains("wcag");
}
/**
* True when a result speaks for the requested profile. Only archival results count, and a level
* B pass must never satisfy a level A request.
*/
private static boolean answersRequest(
PdfaProfile profile,
stirling.software.SPDF.model.api.security.PDFVerificationResult result) {
if (isAccessibilityProfile(result)) {
return false;
}
String standard = result.getStandard();
if (standard == null || standard.length() < 2) {
return false;
}
if (standard.charAt(0) != Character.forDigit(profile.getPart(), 10)) {
return false;
}
return !profile.requiresTagging() || Character.toLowerCase(standard.charAt(1)) == 'a';
}
private void verifyStrictCompliance(byte[] pdfBytes, PdfaProfile profile, boolean levelAReached)
throws IOException {
// Tagging is the only route to level A, so an untagged file cannot answer a strict request.
if (!levelAReached) {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST,
"Strict PDF/A mode enabled: the document could not be tagged, so "
+ profile.getDisplayName()
+ " was not reached. It is valid at level B.");
}
try (InputStream is = new ByteArrayInputStream(pdfBytes)) {
List<stirling.software.SPDF.model.api.security.PDFVerificationResult> results =
veraPDFService.validatePDF(is);
boolean isCompliant = results.stream().anyMatch(result -> result.isCompliant());
boolean isCompliant =
results.stream()
.filter(result -> answersRequest(profile, result))
.anyMatch(
stirling.software.SPDF.model.api.security.PDFVerificationResult
::isCompliant);
if (!isCompliant) {
String details =
results.stream()
@@ -1901,7 +2024,9 @@ public class ConvertPDFToPDFA {
.collect(Collectors.joining("; "));
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST,
"Strict PDF/A mode enabled: Conversion is not perfectly compliant. Details: "
"Strict PDF/A mode enabled: the output is not perfectly compliant with "
+ profile.getDisplayName()
+ ". Details: "
+ details);
}
} catch (Exception e) {
@@ -2466,11 +2591,16 @@ public class ConvertPDFToPDFA {
@Getter
private enum PdfaProfile {
PDF_A_1B(1, "PDF/A-1b", "_PDFA-1b.pdf", "1.4", Format.PDF_A1B, "pdfa-1"),
PDF_A_2B(2, "PDF/A-2b", "_PDFA-2b.pdf", "1.7", null, "pdfa", "pdfa-2", "pdfa-2b"),
PDF_A_3B(3, "PDF/A-3b", "_PDFA-3b.pdf", "1.7", null, "pdfa-3", "pdfa-3b");
PDF_A_1B(1, "B", "PDF/A-1b", "_PDFA-1b.pdf", "1.4", Format.PDF_A1B, "pdfa-1"),
PDF_A_2B(2, "B", "PDF/A-2b", "_PDFA-2b.pdf", "1.7", null, "pdfa", "pdfa-2", "pdfa-2b"),
PDF_A_3B(3, "B", "PDF/A-3b", "_PDFA-3b.pdf", "1.7", null, "pdfa-3", "pdfa-3b"),
// Level A = level B plus tagging, declared language and Unicode text; tagged post-convert.
PDF_A_1A(1, "A", "PDF/A-1a", "_PDFA-1a.pdf", "1.4", Format.PDF_A1B, "pdfa-1a"),
PDF_A_2A(2, "A", "PDF/A-2a", "_PDFA-2a.pdf", "1.7", null, "pdfa-2a"),
PDF_A_3A(3, "A", "PDF/A-3a", "_PDFA-3a.pdf", "1.7", null, "pdfa-3a");
private final int part;
private final String conformanceLevel;
private final String displayName;
private final String suffix;
private final String compatibilityLevel;
@@ -2479,12 +2609,14 @@ public class ConvertPDFToPDFA {
PdfaProfile(
int part,
String conformanceLevel,
String displayName,
String suffix,
String compatibilityLevel,
Format preflightFormat,
String... requestTokens) {
this.part = part;
this.conformanceLevel = conformanceLevel;
this.displayName = displayName;
this.suffix = suffix;
this.compatibilityLevel = compatibilityLevel;
@@ -2495,6 +2627,10 @@ public class ConvertPDFToPDFA {
.toList();
}
boolean requiresTagging() {
return "A".equals(conformanceLevel);
}
static PdfaProfile fromRequest(String requestToken) {
if (requestToken == null) {
return PDF_A_2B;
@@ -2508,8 +2644,11 @@ public class ConvertPDFToPDFA {
return match.orElse(PDF_A_2B);
}
String outputSuffix() {
return suffix;
/**
* Names the file at the level actually reached; a level A name over level B content lies.
*/
String outputSuffix(boolean levelAReached) {
return levelAReached ? suffix : "_PDFA-" + part + "b.pdf";
}
Optional<Format> preflightFormat() {

Some files were not shown because too many files have changed in this diff Show More