Compare commits

..
Author SHA1 Message Date
Ludy87 5e3529fa5b Style: consolidate multiline expressions across engine
Formatting-only changes across the engine: collapsed several multi-line expressions into single-line forms, tightened f-strings and SQL literals, and removed extraneous line breaks/parentheses. Affected files include the tool model generator, multiple agents (contradiction, ledger, math_presentation, pdf_edit, pdf_questions), document stores (pgvector, sqlite, rag_capability), models (tool_io, tool_models), services (tool_io_compat, tracking), settings, and tests. No functional behavior changes intended—these edits are purely stylistic to improve readability and satisfy formatting/linting.
2026-08-11 18:34:45 +02:00
Ludy87 a2606e07b6 Update pre-commit.yml 2026-08-11 18:31:01 +02:00
Ludy fcb474243c Merge branch 'main' into update_python_dep_20260804 2026-08-11 18:30:06 +02:00
Ludy87 54d1bbe04e Update ruff config; apply formatting & noqa fixes
Relax pre-commit pins and increase ruff line-length in engine/pyproject.toml; add per-file ruff ignores and update engine/uv.lock. Apply many non-functional Python formatting and linting adjustments across scripts and test step files: consolidate multi-line strings/args, collapse long f-strings, reorder/clean imports, add # noqa markers (BLE001, E501, N806 etc.), and minor refactors to improve style and silence linters. No behavioral changes intended — changes are formatting/lint-related to satisfy tooling and reduce warnings.
2026-08-11 18:29:09 +02:00
Ludy b7aa0b07c1 Merge branch 'main' into update_python_dep_20260804 2026-08-11 16:54:26 +02:00
Ludy 35ac74b049 fix(ui): improve mobile language selector positioning (#7158)
# Description of Changes

- Made the language selector dropdown responsive on mobile devices.
- Limited the dropdown width to the available viewport width.
- Changed the language grid to use two columns on small screens.
- Added a scrollable maximum height to prevent the dropdown from
overflowing the settings dialog.
- Updated only:
  - `frontend/editor/src/core/components/shared/LanguageSelector.tsx`
-
`frontend/editor/src/core/components/shared/LanguageSelector.module.css`

The change improves usability on narrow mobile screens, where the
previous fixed-width four-column layout caused the language selector to
overflow horizontally.

before:
<img width="1920" height="1032" alt="image"
src="https://github.com/user-attachments/assets/85eeca25-4aad-4280-8a11-18db4899deec"
/>

after:
<img width="1920" height="1080" alt="image"
src="https://github.com/user-attachments/assets/8b3ccc12-fc1f-4949-88b6-4ca65354a3f9"
/>


---

## 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-11 14:18:02 +00:00
brios b56e62caec refactor(sidebar): Replace custom sidebars (bookmark, attachment etc...) with reusable sidebar component (#7143)
# Description of Changes


This helps keeps them consistent and similar/same in regards to design,
colour scheme and so on. Generally, we would want to improve upon this,
as UX bit lacking here and there e.g., with the button
fluidness/conssitency what the button does. Bookmark open sidebar
editor, the other two redirects to the page, those may be made more
consistent.


Changes:

* Refactored `AttachmentSidebar`, `BookmarkSidebar`, and
`CommentsSidebar` to use the new `SidebarBase` component, replacing
custom header, search, and layout code for a more consistent and
maintainable UI.
* Added search functionality to `CommentsSidebar`, including filtering
by comment content and author, and displaying a message when no comments
match the search.
* Updated search placeholders and empty state messages for attachments,
bookmarks, and comments to use translation keys, improving localization
and user feedback.


### New

<img width="1490" height="1816" alt="image"
src="https://github.com/user-attachments/assets/71de659e-388e-4aef-bf03-4c5ee7948a2f"
/>


### Old

<img width="1574" height="1726" alt="image"
src="https://github.com/user-attachments/assets/4604653c-865f-4e75-9737-4b61237efcc6"
/>


<!--
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-11 14:17:36 +00:00
Anthony Stirling 8b1bfb87f7 Add S3 Object Lock retention to policy outputs (#7094)
# Description of Changes

Add S3 Object Lock retention to policy outputs (create file and cant be
deleted untill after a set deadline passed)

---

## 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-11 14:04:32 +00:00
ConnorYoh c146f7e877 chore(saas): drop the last dead Flyway migration (#7433)
Removes
`app/saas/src/main/resources/db/migration/saas/V33__api_keys.sql`, the
only file left in that tree.

## Flyway does not run

There is no Flyway dependency in any `build.gradle` and no Flyway
configuration in any properties file. Nothing has executed these for
some time, so `V33` was never applied by anything.

SaaS schema has exactly two writers today:

1. Supabase CLI migrations in the `Stirling-PDF-SaaS` repo, applied by
that repo's PR CI
2. Hibernate `ddl-auto=update` in the app, which only ever adds

## Why delete rather than leave it

A directory of plausible-looking migrations is a trap. The next person
to make a schema change adds a `V34`, assumes it will run, and it
silently does not. I nearly did exactly that while working on
[#7414](https://github.com/Stirling-Tools/Stirling-PDF/pull/7414) before
checking whether Flyway was actually wired.

## Nothing is lost

Both tables it declared, `api_keys` and `api_key_daily_usage`, have JPA
entities (`ApiKey`, `ApiKeyDailyUsage`), so `ddl-auto` creates them.
That is already how they exist on every deployment that has them, since
Flyway was not the thing creating them.

## Checks

No references anywhere: nothing in code, config, gradle or docs mentions
`db/migration`, `flyway` or `V33`. Two code comments mention Flyway
historically, explaining why a column looks the way it does; those are
accurate history and are left alone.

`:saas:compileJava` and `:saas:processResources` green after the
removal.
2026-08-11 12:53:56 +00:00
James Brunton 6eca6ba65c Change frontend CI to only run tests once (#7431)
# Description of Changes
Frontend CI is currently set to run `task frontend:check:all`, which
runs the tests, and then manually runs them a second time with coverage
enabled. This adds about 4 minutes onto the runtime of the frontend
tests for no reason. This PR changes the `frontend:test` rule to respond
to the `COVERAGE` and `CI` env vars to enable coverage analysis of the
frontend tests if the setting tells them to.
2026-08-11 12:50:48 +00:00
ConnorYohandReece Browne df170fd4a6 feat(storage): encryption-at-rest ops — audit, admin kill switch, migration, key rotation (PR2) (#7173)
# Description of Changes

**PR2 of the encrypt-at-rest initiative — PR1 was #7155** Makes the P1
crypto operable and compliance-credible: admins can see the feature's
state, flip the kill switch over an API instead of raw SQL, encrypt the
pre-existing plaintext backlog, rotate the master key, and every
security-relevant event lands in the audit trail. No frontend — that's
PR3.

**What was changed**

- **Audit events** — new `STORAGE_ENCRYPTION` audit type, emitted
through a small listener interface so the crypto classes stay plain
objects: `encrypt`, `decrypt` (per-read events honour
`storage.encryption.auditReads`, default **on** — HIPAA reviewers expect
read audit; busy installs can disable), `decrypt.denied` (always),
`key.created/disabled/enabled`, `master.rotated`, `migration.completed`,
plus a `plaintextExport` marker whenever a plaintext copy of
encrypted-at-rest content is served (with `inline` flag to distinguish
in-app view from saved download).
- **Admin API** `/api/v1/admin/storage-encryption` (`hasRole('ADMIN')`):
- `GET /status` — write/decrypt state, **master-key fingerprint**
(SHA-256 prefix for backup verification, never key material), encrypted
vs plaintext file counts, full key list with status history.
- `POST /keys/{id}/disable` / `enable` — the kill switch, now with
active cache invalidation so revocation is immediate on the handling
node (cross-node converges within the 60s cache TTL). Enable is
restricted to DISABLED keys so two ACTIVE keys can't exist per scope.
  - `POST /migrate` + `GET /migrate/status` — encrypt-existing job.
- `POST /master/rotate` — key material is never accepted over HTTP; keys
come from config/env.
- **Deliberately no delete endpoint** — key material can be disabled but
never destroyed through the API.
- **Encrypt-existing migration job** — new writes are encrypted from the
moment the flag is on; this converts the backlog. Crash-safe per file:
store the encrypted copy under a NEW storage key → compare-and-swap the
DB row → only then delete the old blob. A CAS miss (user replaced the
file mid-run) discards the job's copy — the user's file always wins.
Worst crash outcome is an orphaned blob, never a lost file; re-runs are
idempotent (`encryption_key_id IS NULL` selection, cursor-paged so
failures can't wedge the loop). Handles all three blobs per row
(main/history/audit-log), runs on a throttled virtual thread,
single-flight guarded.
- **Master-key rotation** — cheap by design thanks to the P1 hierarchy:
rotate re-wraps the handful of KEK rows, zero file I/O. New config
`stirling.security.fileEncryptionKeyPrevious` (+env) gives `unwrap` a
fallback during rotation, and
`stirling.security.fileEncryptionKeyVersion` marks which master wrapped
each row. Runbook: set new key primary + old as previous + bump version
→ restart (startup self-check passes via fallback, warns about pending
rows) → `POST /master/rotate` → remove the previous key.
- **Shared state bean** — `StorageEncryptionState` is built once and
shared by the storage decorator and the admin API, so kill-switch cache
invalidation hits the same caches the decorator reads.

**Reviewer notes**

- The revoked→403 mapping promised for PR2 already landed in #7155 after
manual testing; this PR adds the matching `decrypt.denied` audit event.
- 19 new tests: audit emission (encrypt/decrypt/denied, legacy plaintext
emits nothing), kill-switch immediacy (no TTL wait), rotation
(previous-key fallback, re-wrap + cleanup, idempotent second call),
migration (backlog encrypted byte-identical, CAS-miss discards own copy,
per-file failure counting, concurrent-start rejection, write-disabled
rejection), admin controller status/conflict/not-found paths.
- Full proprietary suite: 2246/2247 green (the one failure is the
pre-existing Windows-symlink FolderIdentitiesTest, unrelated).

---


[ENCRYPTION_AT_REST_TEST_REPORT.html](https://github.com/user-attachments/files/30664158/ENCRYPTION_AT_REST_TEST_REPORT.html)

---------

Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
2026-08-11 12:01:58 +00:00
James Brunton 998e851130 Support using Convert in Pipelines (#7290)
# Description of Changes
Convert tool is currently unavailable in pipelines because it's a very
complex tool so conversions to and from the backend types was previously
skipped for time. This PR redesigns the tool setup so that it can be
used in pipelines and the existing editor/automate uses. There's
probably more work to do one day to have a proper second look at Convert
because the rest of the code has changed so much since it was first
written, but this adds a bit of type safety to it and at least adopts
the bidirectional tool APIs so we're moving in the right direction.

<img width="1263" height="581" alt="image"
src="https://github.com/user-attachments/assets/58d64894-05a4-41e0-a840-3202dd8e0e39"
/>

<img width="1244" height="578" alt="image"
src="https://github.com/user-attachments/assets/79285c2d-6777-4df7-a8fa-1453cb8a8258"
/>

<img width="1249" height="584" alt="image"
src="https://github.com/user-attachments/assets/8ced6bb5-817f-4911-9ada-c3707a777243"
/>

<img width="1245" height="510" alt="image"
src="https://github.com/user-attachments/assets/d01a083c-fb76-4c8c-a526-99613afc2608"
/>
2026-08-11 10:40:45 +00:00
Anthony Stirling 8db44ddbaf feat(policies): default Security policy to run on export (#7407)
# Description of Changes

default Security policy to run on export and not import 
---

## 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-11 10:38:20 +00:00
Anthony Stirling e6680c4cf5 fix(auth): remove signup page from self-hosted builds (#7406)
# Description of Changes

remove signup page from self-hosted builds since all signups are either
default or done via admins
---

## 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-11 10:16:28 +00:00
05eb74022a chore(ci): migrate Python tooling to uv and standardize workflow execution (#7386)
# Description of Changes

This PR modernizes the project's Python tooling across GitHub Actions by
migrating CI workflows from pip-based dependency management to `uv` and
aligning Python execution with the engine project's managed environment.

### What was changed

- Replaced `actions/setup-python` and ad-hoc `pip install` steps with
`astral-sh/setup-uv` across CI workflows.
- Configured shared `uv` dependency caching using
`engine/pyproject.toml` and `engine/uv.lock`.
- Updated Python script execution to use `uv run --project engine
--locked` for a consistent runtime environment.
- Replaced package installation steps with `uv sync` for the required
dependency groups (e.g. `tools` and `cucumber`).
- Added Docker image build validation for both production and
development AI engine images.
- Updated workflow cache configuration and Docker build context where
required.
- Removed obsolete Python requirements files that are no longer needed
after the migration.
- Applied minor Python code modernizations, including import cleanup,
modern built-in generic type annotations (`list[...]`, `tuple[...]`,
`float | None`), and small style improvements.
- Removed unnecessary Python formatter/linter extensions from the
development container configuration.

### Why the change was made

- Standardize Python dependency management across the repository.
- Reduce duplicated dependency installation logic in CI.
- Improve workflow performance through shared dependency caching.
- Ensure all Python utilities execute against the same locked dependency
set managed by the engine project.
- Simplify long-term maintenance by eliminating legacy requirements
files and pip-specific workflow steps.


---

## 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: Carsten Drewes <c.drewes@stud.uni-hannover.de>
Co-authored-by: albanobattistella <34811668+albanobattistella@users.noreply.github.com>
Co-authored-by: kastenherri <116314318+kastenherri@users.noreply.github.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: James Brunton <jbrunton96@gmail.com>
2026-08-11 08:09:21 +00:00
stirlingbot[bot] bbaff8d6c4 Sync portal docs from docs repo (#7408)
Auto-generated by stirlingbot[bot].

Regenerates `frontend/editor/src/portal/generated/docsManifest.json`
from the Stirling docs repo via `npm run docs:sync`.

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-10 16:55:34 +00:00
EthanHealy01 c929386442 Record policy-run failures as durable, actionable events (Review Flow PR 1) (#7269)
# Description of Changes

PR 1 of the failure-notification work: a durable, team-scoped record of
**why a policy run failed**, surfaced in the portal with the triage
actions each failure allows.

Today a failed policy run is not quite invisible, but it is unusable:
the ledger marks the file `ERROR`, and the audit aspect keeps the
exception message and status code. Nothing classifies either one,
nothing surfaces them, and neither offers a next step. If the file came
from a folder, bucket or webhook there is also no user watching, so
nobody learns it never made it through. This adds the record and the
read surface; the remediation that acts on documents comes later (see
below).

## What this does

**A failure kind registry as data.** `FailureKind` describes what can go
wrong: a stable wire id, i18n keys, an English fallback, and four facets
the review surface needs (`Stage`, `Severity`, `Remedy`, `Scope`). It is
shaped like the existing `ExceptionUtils.ErrorCode` and *links* to that
vocabulary rather than replacing it.

**Classification off structured codes, not message matching.** Policy
steps dispatch over loopback HTTP, so a tool's 4xx arrives as a
`RestClientResponseException` whose body is the Problem Details document
carrying `errorCode`. `FailureClassifier` reads that. Anything
unrecognised becomes `UNKNOWN`, which is the point: every failed run
gets an addressable record from day one, and which kinds to promote next
is answered by production frequency rather than guesswork.

**Actions declared by a kind, implemented as beans.** A kind lists the
`FailureActionId`s it offers; behaviour lives in `FailureAction` beans
resolved by id — the idiom this codebase already uses for `InputSource`,
`PolicyOutputSink` and `PolicyTrigger`. A kind cannot be sent an action
it never declared (400), so an incoherent pairing is unreachable rather
than merely unrendered. A new kind ships as a registry entry plus copy:
no new endpoint, no UI change.

**Repeat folding.** Recording folds a genuine repeat into the existing
incident instead of inserting again, keyed on `(team_id, dedup_key)`.
That matters for a snapshot-mode source that re-lists every file on each
sweep: the same broken file is one incident, not one per sweep. Distinct
files keep distinct rows. The unique constraint is enforced by the
database, and a writer that loses the insert race folds into the
winner's row.

One granularity caveat worth naming: nothing populates `file_id` in this
PR, so every row has it NULL. A FILE-scoped kind therefore dedups on
`policy + run` rather than `policy + file`. That still yields one row
per document for the sources shipped here, because the folder, S3 and
webhook sources each start one run per file; it stops holding as soon as
a single run carries several documents, which is why editor-origin
reporting (item 3 below) populates `file_id`.

**No document identity is stored.** No file name, no content. `fileId`
is an opaque reference only the owner's own client can resolve locally.
`detail` keeps the raw message (the only diagnostic an `UNKNOWN` failure
has) with anything path- or filename-shaped stripped on the way in,
capped at 2,000 characters. `PolicyExecutor`'s type-mismatch message now
reports the *extension* rather than the filename, since that message
becomes the stored `detail`.

**Access.** Reads and triage are leader-only, gated exactly the way
`PolicyController` gates policy editing, with the single-user carve-out
when login is disabled. Every read and write is scoped to the caller's
own team from the authenticated principal — there is no team parameter
on the API.

Self-hosted needs no migration: the table is created from the entity by
`ddl-auto=update`, as with every other table.

## What this does not do yet

- **Actions are incident dispositions, not document dispositions.**
Acknowledge and Dismiss change how a failure is displayed and touch
nothing else — not the document, not the processed-file ledger, not the
run, not any output destination. That is what makes them safe to offer
against `UNKNOWN`, and why there is no Approve/Release yet.
- **Two kinds only.** `INPUT_PASSWORD_PROTECTED` and `UNKNOWN`.
Everything else classifies as `UNKNOWN` and shows its raw message.
- **Editor-origin failures are not reported.** Every row is `PROCESSOR`.
`FailureOrigin.EDITOR` and `API` exist in the enum but nothing writes
them.
- **The list is dev-only for now.** The section renders behind
`import.meta.env.DEV`, so it ships in no production bundle. The
endpoints are live and gated.
- **No retention or per-team cap** on `file_run_events`. Tracked
separately.
- **No suspend-and-prompt.** `PolicyInputRequiredException` and the
engine's `suspend()` exist but nothing throws it, so a run cannot pause
to ask for a password today.
- **SaaS needs a migration** in `Stirling-PDF-SaaS` (`CREATE TABLE IF
NOT EXISTS stirling_pdf.file_run_events`), per the convention documented
at `app/saas/src/main/resources/application-saas.properties:21`.

## What follows in later PRs

1. **Map the remaining error codes to specific kinds** — corrupted file,
OCR unavailable, output destination unreachable, entitlement refusals,
and so on — each with its own copy and its own action set, replacing
today's `UNKNOWN` catch-all with a named notification in the review UI.
2. **Real remediation actions** attached to those kinds: fix (supply a
password and resume), skip (drop this file, continue the batch), and
decline (reject an incoming file outright), acting on the held document
rather than only on the incident row. This is where the
suspend-and-prompt path gets wired.
3. **Editor-origin reporting**, so a failure a user hits in the editor
lands in the same queue as one from a bucket.
4. **The user-facing review surface**: notifications with a sticky
review section, per-file badges, and an export gate, with the dev-only
list here replaced by the real thing.

## How to test

Needs a SaaS or proprietary build with login enabled, and an account
that leads a team.

1. Create a policy in the Processor with any step (Auto-redact is fine)
and a source you can drop files into.
2. Upload two files that will fail it: **a password-protected PDF**, and
**a corrupted PDF** (truncate a valid one, or rename a `.csv` to
`.pdf`).
3. Let the policy run and fail on both.
4. Go to the portal's **Documents** view and scroll to **Failures** (dev
builds only).

Expect two rows:

- **Password-protected document** — classified from `E004`, with the
kind's own labels **"I'll unlock this"** and **"Skip this file"** rather
than generic wording.
- **Unrecognised failure** — the corrupted file, classified `UNKNOWN`
(`E001` is not claimed by a kind yet), showing its raw message with
generic **Acknowledge** / **Dismiss**.

Neither row contains a file name anywhere, including in the raw message.
Press **Show raw JSON** to read exactly what the server returned. Acting
on a row transitions it and comes back with both buttons disabled and a
reason.

Re-running the same batch increments the occurrence count on the
existing rows rather than adding new ones; two *different*
password-protected files produce two separate rows.

---

## 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
2026-08-10 16:45:13 +00:00
James Brunton 0ff4ef629c New Pipeline UI redesign (#7202)
# Description of Changes
Supersedes #7144.

Redesign the Processor New Pipeline page to use a graph-based interface.
Far from perfect at this stage but I'm pretty happy with the
interactions on the graph itself. The bar at the top needs some work to
make it prettier and more clear what everything is for, but I'd rather
get this in and do changes in a follow-up PR because this is big enough
on its own and leaves us better than where we were before.

<img width="1270" height="598" alt="image"
src="https://github.com/user-attachments/assets/fa16f7d0-058d-4929-83a2-3c8e42859970"
/>

<img width="1511" height="788" alt="image"
src="https://github.com/user-attachments/assets/bbeb411f-dbf9-43c9-9b34-28b3a5917e5b"
/>

<img width="1261" height="754" alt="image"
src="https://github.com/user-attachments/assets/380fca1f-eed6-44fe-819d-c10598472583"
/>

<img width="1256" height="653" alt="image"
src="https://github.com/user-attachments/assets/59e9a673-b026-43a9-b7f1-ebaf89236df2"
/>

<img width="1216" height="726" alt="image"
src="https://github.com/user-attachments/assets/b34ae323-6842-44d5-b054-eacf08647ad7"
/>
2026-08-10 16:05:05 +00:00
ConnorYohandReece Browne 35a861f4f8 feat(editor): move endpoint availability onto TanStack Query (#7285)
# Description of Changes

> Stacked on #7264, sibling of #7283. Independent of #7283 — the only
overlap is two additive lines in `core/query/keys.ts` and
`core/api/config.ts`. Either can merge first.

## The problem

`useEndpointConfig` kept its own cache: a module-level `globalFetchDone`
boolean, a mutable `globalEndpointCache` object, and a
`resetGlobalCache()` called from the JWT listener. Which consumer
mounted first decided who paid for the request, and nothing invalidated
it except a page reload.

## End state

One shared query for the whole availability map; each of the 12
consumers projects the endpoints it asked for. **251 lines to 101**,
same return shape, no consumer changes.

| | Before | After |
|---|---|---|
| Cross-consumer cache | `globalFetchDone` + mutable module object |
query key |
| Invalidation | `resetGlobalCache()` mutating that object |
`invalidateQueries` |
| Per-endpoint check | own `useState` triple | query keyed by endpoint |

Behaviour kept deliberately:

- **Unknown endpoints and any failure still read as enabled.** This
fires before auth settles, and disabling every tool on a hiccup is worse
than letting one call fail later.
- **`retry` is off for the availability map.** The fallback *is* the
answer, so retrying only doubles a request every logged-out visitor
makes on load.

## Desktop is untouched

`desktop/hooks/useEndpointConfig.ts` shadows this module entirely — no
shared code, so core converting doesn't affect it and there's no
half-migrated state.

It's 482 lines of orchestration rather than fetching: dependency-ready
gating, `tauriBackendService` and `selfHostedServerMonitor`
subscriptions, a 2.5s timeout retry for backend startup, a legacy
`?endpoints=` fallback for old servers, and SaaS-routing optimism that
rewrites disabled endpoints to enabled. It also has no test coverage to
convert against, and it decides whether tools appear at all in the
desktop app.

That's a different job from this one and wants its own review. Next PR.

## Testing

9 new tests: projection onto the requested subset, one request across
consumers, unknown-endpoint fallback, failure fallback with no retry,
empty-list no-fetch, JWT invalidation, and the three single-endpoint
cases.

`task frontend:check` green: 1677 tests across 192 files, typecheck on
all five flavours, eslint, dpdm, prettier.

Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
2026-08-10 12:42:44 +00:00
Anthony Stirling 59ed4f5fd1 Fix automate unrunnable tools (#7311)
# Description of Changes

Fix automate unrunnable tools


## Problem

- Remove Image failed in Automate with `Tool operation not supported:
removeImage`
- Its registry entry had `operationConfig: undefined` even though the
config existed and was already tested
- The Automate picker only filtered on `supportsAutomate`, never on
`operationConfig` — so broken tools were selectable and failed only at
run time

## Fixes

- Wire up `removeImage` and `pageLayout` operation configs (both already
existed, just never registered)
- Exclude `validateSignature` (report tool, not on the operationConfig
seam) and `scannerEffect` (no frontend implementation) via
`supportsAutomate: false`
- Picker now also filters on `operationConfig`, so this class of bug
can't reach users again
- `overlay-pdfs` returns 400 instead of 500 when overlay files or mode
are missing
- Fix `new URL().pathname` Windows path bug that stopped 2 test suites
from loading

---

## 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-10 11:14:56 +00:00
Reece Browne 7bf18cc4c7 Storybook: render off the app's real theme CSS, stop hardcoding story colours, and gate a11y in dark mode too (#7187)
## What

Makes Storybook render components with the same CSS the app gives them.

- The preview loaded the token primitives but **not the editor's
semantic token layer** (`styles/theme.css`), so components styled on
those variables rendered unthemed — three onboarding stories were
importing it by hand to stop their modal surfaces rendering transparent.
It's now loaded in the preview and the workarounds are gone.
- **Portal stories render inside the `.portal-scope` wrapper** PortalApp
mounts, so the portal's scoped reset and typography apply to them
exactly as in the app — and, deliberately, to nothing else.
- The folder stories invented their own hex colours, two of which aren't
values the app's `FOLDER_COLOR_PALETTE` can produce. They now use the
palette, so they can't drift from what a user can actually pick.

Deliberately does **not** load `tailwind.css` — tailwind is on its way
out of the editor, so matching the token layer alone is the target
state.

## Story colours route through the tokens, enforced

Stories were exempt from the `code-colors` lint, and it showed:
hardcoded hexes for surfaces the tokens already name (chat bubbles,
borders, demo backgrounds), `var(--x, #hex)` fallbacks that mask a
renamed token by silently painting the stale colour, and mocked category
accents for which real `--color-cat-*` tokens exist.

- Styling literals now use tokens; the dead fallbacks are stripped.
- The stories exemption is removed from `theme-lint`, so this can't
regress.
- Colours that are **the datum itself** — `ColorInput` values, signature
ink, per-policy accents, brand-mark swatches — stay literal via
`theme-allow-color`, hoisted to named consts so the exemption and its
reason sit together.

A practical side effect: stories styled on tokens actually respond to
the dark-mode toolbar toggle, which is what makes a dark-theme a11y pass
meaningful later.

## The a11y gate now runs dark as well as light

Contrast is most of what axe reports and it is theme-dependent, so a
light-only gate left half the surface unmeasured — and it only becomes
measurable at all once the tokens above actually flip. `SCAN_THEME=dark`
pins the theme for a whole scan run, every a11y task runs both themes,
and each theme has its own baseline:

- **light** re-recorded against the themed rendering (the old baseline
measured colours the app never shows): 831 stories with violations
- **dark** recorded for the first time: 798 stories with violations, 980
story-rule pairs, zero render failures across the full sweep

Verified end to end: dark scans measure against dark surfaces (`#18181b`
vs `#ffffff`), both baselines self-check clean, and a live scan of
stories that changed on main after recording passes both gates.
Nightly's timeout doubles for the second sweep.

## Testing

Typecheck (all variants), ESLint and Prettier pass. Onboarding, folder,
portal and control stories render in the browser scan (39/39) with the
per-story CSS imports removed; every story touched by the colour sweep
renders too (58/58). `task frontend:lint:colors` passes with stories
included.
2026-08-10 11:06:18 +00:00
James Brunton af5f54274d Add defaults to calculations for ToolIO (#7289)
# Description of Changes
Currently when calculating the output file type for some tools, the
system will get it wrong because it doesn't know about what the default
parameters in tools are, so if it doesn't have a value for some key,
it'll just bail out and say "it might not be compatible". This PR adds
logic to `ToolIO` to read the default values set for the parameters if
the tool has `ToolIOCase`s and takes them into account when figuring out
the output type. I've built it with horrible Java reflection magic to
avoid having to specify the default for params twice, which will make it
impossible for the defaults to disagree with each other. This just runs
once at startup so there's negligible performance impact.

The change is easily tested with Change Parameters, which is just
`add-password` behind the scenes but with the password params omitted
(so Change Password is always PDF->PDF, never encrypted like Add
Password).

Also (somewhat hackily) fixes a bug I noticed where saving a Change
Permissions step then leaving and returning to the pipeline will cause
the step to be reloaded as Add Password. I've added a system to
disambiguate tools which share the same endpoint (which is only these
two currently).

## Currently

<img width="455" height="135" alt="image"
src="https://github.com/user-attachments/assets/c8867d6a-599b-4f21-a2db-4a1b6ac22d73"
/>

## Now

<img width="415" height="122" alt="image"
src="https://github.com/user-attachments/assets/bd8d1bab-f00a-421b-8c91-5af0d2ad5335"
/>
2026-08-10 11:00:16 +00:00
James Brunton 78acd9a14b Run Playwright on all platforms in PRs (#7304)
# Description of Changes
Nightlies keep failing because the Playwright tests only run on Chrome
in PRs. This PR changes it so that we run all 3 browsers in all
(frontend) PRs so we catch these things before they merge in. They run
in parallel so it won't take any more time for the CI to finish.
2026-08-10 10:41:07 +00:00
James Brunton cfaf777f2b Fix more any type usages in frontend code (#7334)
# Description of Changes
Continued effort towards removing all uses of the any type in our
frontend code (last PR was #7326). This PR fixes 7 more folders and
removes them from the exclude list. All of them were localised within
the folder in the exclude list so again were pretty easy to fix.
2026-08-10 10:40:47 +00:00
brios 4e901f7524 refactor(package): rename example classes and update package structure (#7400)
# Description of Changes

Refactors a small set of previously vendored/derived PDFBox "example"
classes into Stirling’s own package namespaces.



<!--
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-09 20:41:57 +01:00
Ludy 0c0a96aa83 build(deps): bump org.simplejavamail from 8.12.0 to 9.2.0 (#7398)
Bump org.simplejavamail:simple-java-mail and outlook-module from 8.12.6
to 9.2.0 in app/common/build.gradle. Add a Dependabot group for the
org.simplejavamail artifacts in .github/dependabot.yml so updates for
those packages are grouped into a single PR.


---

## 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-09 20:30:33 +01:00
Anthony Stirling faba9f53ea Update all translations (#7380)
# Description of Changes

Update all translations

---

## 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-09 12:45:32 +01:00
Ludy c1275763af chore(i18n): localize hardcoded UI strings across editor components (#7172)
# Description of Changes

This PR replaces multiple hardcoded English UI strings with translation
keys to improve localization consistency throughout the editor.

### What was changed

- Added new translation entries for:
  - AI chat panel header and empty state
  - Generic dropdown placeholders and empty states
  - Generic input placeholder (`Enter value`)
  - Tool renderer "tool not found" message
  - Signature pen size placeholder
- Updated shared components to use translated fallback placeholders
instead of hardcoded English text:
  - `DropdownListWithFooter`
  - `EditableSecretField`
  - `GroupedFormatDropdown`
  - `LanguagePicker`
  - `PenSizeSelector`
- Localized the AI chat panel:
  - Assistant title
  - Empty state message
  - Input placeholder
- Localized the fallback error message displayed when a tool cannot be
resolved.

### Why the change was made

Several shared UI components and the AI assistant interface contained
hardcoded English strings, preventing proper localization and creating
an inconsistent multilingual experience. Moving these strings into the
translation system ensures they can be translated alongside the rest of
the application and provides reusable defaults for shared components.


---

## 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-09 12:44:39 +01:00
kastenherri 3260f5415d localization/formFill: Improve german translation and add localization (#7255)
# Description of Changes
The formFill tool name and description as well as the notification popup
for forms were missing their german translations. There were also some
hardcoded text elements in the UI.

Added translations of formFill-related tags and new tags and translation
method for hardcoded strings.

<!--
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)
- [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
- [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)

- [ ] 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.

---------

Signed-off-by: Carsten Drewes <c.drewes@stud.uni-hannover.de>
2026-08-09 12:44:16 +01:00
albanobattistella 0286c716de Updated Italian translation (#7353) 2026-08-09 10:38:24 +00:00
dependabot[bot]andAnthony Stirling b7a6a96995 build(deps): bump docker/setup-qemu-action from 4.0.0 to 4.2.0 (#7248)
Bumps
[docker/setup-qemu-action](https://github.com/docker/setup-qemu-action)
from 4.0.0 to 4.2.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/docker/setup-qemu-action/releases">docker/setup-qemu-action's
releases</a>.</em></p>
<blockquote>
<h2>v4.2.0</h2>
<ul>
<li>Preserve names in esbuild bundle by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/setup-qemu-action/pull/311">docker/setup-qemu-action#311</a></li>
<li>Bump <code>@​actions/core</code> from 3.0.0 to 3.0.1 in <a
href="https://redirect.github.com/docker/setup-qemu-action/pull/295">docker/setup-qemu-action#295</a></li>
<li>Bump <code>@​docker/actions-toolkit</code> from 0.91.0 to 0.92.0 in
<a
href="https://redirect.github.com/docker/setup-qemu-action/pull/315">docker/setup-qemu-action#315</a></li>
<li>Bump <code>@​sigstore/core</code> from 3.1.0 to 3.2.1 in <a
href="https://redirect.github.com/docker/setup-qemu-action/pull/312">docker/setup-qemu-action#312</a></li>
<li>Bump js-yaml from 4.1.1 to 4.2.0 in <a
href="https://redirect.github.com/docker/setup-qemu-action/pull/310">docker/setup-qemu-action#310</a></li>
<li>Bump tmp from 0.2.6 to 0.2.7 in <a
href="https://redirect.github.com/docker/setup-qemu-action/pull/304">docker/setup-qemu-action#304</a></li>
<li>Bump undici from 6.26.0 to 6.27.0 in <a
href="https://redirect.github.com/docker/setup-qemu-action/pull/308">docker/setup-qemu-action#308</a></li>
<li>Bump vite from 7.3.2 to 7.3.6 in <a
href="https://redirect.github.com/docker/setup-qemu-action/pull/307">docker/setup-qemu-action#307</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/setup-qemu-action/compare/v4.1.0...v4.2.0">https://github.com/docker/setup-qemu-action/compare/v4.1.0...v4.2.0</a></p>
<h2>v4.1.0</h2>
<ul>
<li>Add <code>reset</code> input to uninstall current emulators by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/setup-qemu-action/pull/21">docker/setup-qemu-action#21</a></li>
<li>Bump <code>@​docker/actions-toolkit</code> from 0.77.0 to 0.91.0 in
<a
href="https://redirect.github.com/docker/setup-qemu-action/pull/250">docker/setup-qemu-action#250</a>
<a
href="https://redirect.github.com/docker/setup-qemu-action/pull/247">docker/setup-qemu-action#247</a></li>
<li>Bump brace-expansion from 1.1.12 to 1.1.15 in <a
href="https://redirect.github.com/docker/setup-qemu-action/pull/265">docker/setup-qemu-action#265</a></li>
<li>Bump fast-xml-builder from 1.0.0 to 1.2.0 in <a
href="https://redirect.github.com/docker/setup-qemu-action/pull/286">docker/setup-qemu-action#286</a></li>
<li>Bump fast-xml-parser from 5.4.2 to 5.8.0 in <a
href="https://redirect.github.com/docker/setup-qemu-action/pull/255">docker/setup-qemu-action#255</a></li>
<li>Bump flatted from 3.3.3 to 3.4.2 in <a
href="https://redirect.github.com/docker/setup-qemu-action/pull/257">docker/setup-qemu-action#257</a></li>
<li>Bump glob from 10.3.15 to 10.5.0 in <a
href="https://redirect.github.com/docker/setup-qemu-action/pull/254">docker/setup-qemu-action#254</a></li>
<li>Bump handlebars from 4.7.8 to 4.7.9 in <a
href="https://redirect.github.com/docker/setup-qemu-action/pull/262">docker/setup-qemu-action#262</a></li>
<li>Bump lodash from 4.17.23 to 4.18.1 in <a
href="https://redirect.github.com/docker/setup-qemu-action/pull/273">docker/setup-qemu-action#273</a></li>
<li>Bump postcss from 8.5.6 to 8.5.10 in <a
href="https://redirect.github.com/docker/setup-qemu-action/pull/285">docker/setup-qemu-action#285</a></li>
<li>Bump tar from 6.2.1 to 7.5.15 in <a
href="https://redirect.github.com/docker/setup-qemu-action/pull/287">docker/setup-qemu-action#287</a></li>
<li>Bump tmp from 0.2.5 to 0.2.6 in <a
href="https://redirect.github.com/docker/setup-qemu-action/pull/291">docker/setup-qemu-action#291</a></li>
<li>Bump undici from 6.23.0 to 6.26.0 in <a
href="https://redirect.github.com/docker/setup-qemu-action/pull/251">docker/setup-qemu-action#251</a></li>
<li>Bump vite from 7.3.1 to 7.3.2 in <a
href="https://redirect.github.com/docker/setup-qemu-action/pull/271">docker/setup-qemu-action#271</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/setup-qemu-action/compare/v4.0.0...v4.1.0">https://github.com/docker/setup-qemu-action/compare/v4.0.0...v4.1.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/docker/setup-qemu-action/commit/96fe6ef7f33517b61c61be40b68a1882f3264fb8"><code>96fe6ef</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/setup-qemu-action/issues/315">#315</a>
from docker/dependabot/npm_and_yarn/docker/actions-to...</li>
<li><a
href="https://github.com/docker/setup-qemu-action/commit/31f08d3fc9186dbe4b4550696f2e32e9aa7f9465"><code>31f08d3</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/setup-qemu-action/commit/4e7017a474d2cf3912bb0437f7fafec6d5fb6c52"><code>4e7017a</code></a>
build(deps): bump <code>@​docker/actions-toolkit</code> from 0.91.0 to
0.92.0</li>
<li><a
href="https://github.com/docker/setup-qemu-action/commit/0eca235293ca1939b58c082f69bdc981ccce8c94"><code>0eca235</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/setup-qemu-action/issues/314">#314</a>
from crazy-max/fix-yarn-preapprove-actions-toolkit</li>
<li><a
href="https://github.com/docker/setup-qemu-action/commit/ea66a4130b037e7961e14a0e5b155836e797cced"><code>ea66a41</code></a>
chore: allow actions-toolkit to bypass yarn age gate</li>
<li><a
href="https://github.com/docker/setup-qemu-action/commit/451542b03ae7946b7082a398b11c8c315a0e4e80"><code>451542b</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/setup-qemu-action/issues/308">#308</a>
from docker/dependabot/npm_and_yarn/undici-6.27.0</li>
<li><a
href="https://github.com/docker/setup-qemu-action/commit/532ae0057542ec2102e2d19e9feccf85f1f69013"><code>532ae00</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/setup-qemu-action/commit/b6f5af659afad3f9931b782668dee4595ae7e841"><code>b6f5af6</code></a>
build(deps): bump undici from 6.26.0 to 6.27.0</li>
<li><a
href="https://github.com/docker/setup-qemu-action/commit/cf96b86294b57480ac6d330bd177fca87eac95bc"><code>cf96b86</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/setup-qemu-action/issues/304">#304</a>
from docker/dependabot/npm_and_yarn/tmp-0.2.7</li>
<li><a
href="https://github.com/docker/setup-qemu-action/commit/f0ba643f78dc96bc931fb83e5dadc39628e10047"><code>f0ba643</code></a>
[dependabot skip] chore: update generated content</li>
<li>Additional commits viewable in <a
href="https://github.com/docker/setup-qemu-action/compare/ce360397dd3f832beb865e1373c09c0e9f86d70a...96fe6ef7f33517b61c61be40b68a1882f3264fb8">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=docker/setup-qemu-action&package-manager=github_actions&previous-version=4.0.0&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>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2026-08-08 21:44:33 +01:00
stirlingbot[bot] 4ed2f15122 Update Frontend 3rd Party Licenses (#7329)
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-08 21:44:18 +01:00
dependabot[bot] 039e29f780 build(deps): bump actions/setup-java from 5.2.0 to 5.7.0 (#7336)
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/be666c2fcd27ec809703dec50e508c2fdc7f6654...b6effb05e454b25005698d916606bdc6ffcbf961">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-java&package-manager=github_actions&previous-version=5.2.0&new-version=5.7.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-08 21:43:56 +01:00
dependabot[bot] fde1e5bb2a build(deps-dev): bump the react group across 1 directory with 2 updates (#7337)
Bumps the react group with 2 updates in the /frontend directory:
[@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react)
and
[@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom).

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

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


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-08 21:43:42 +01:00
dependabot[bot] 6d08cb2f52 build(deps): bump com.drewnoakes:metadata-extractor from 2.20.0 to 2.21.0 (#7338)
Bumps
[com.drewnoakes:metadata-extractor](https://github.com/drewnoakes/metadata-extractor)
from 2.20.0 to 2.21.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/drewnoakes/metadata-extractor/releases">com.drewnoakes:metadata-extractor's
releases</a>.</em></p>
<blockquote>
<h2>2.21.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Replace custom number formatting in IccDescriptor with String.format
by <a href="https://github.com/drewnoakes"><code>@​drewnoakes</code></a>
with <a href="https://github.com/Copilot"><code>@​Copilot</code></a> in
<a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/721">drewnoakes/metadata-extractor#721</a></li>
<li>Fix OOM DoS vector in BmpHeaderDescriptor.formatHex via unbounded
digits parameter by <a
href="https://github.com/drewnoakes"><code>@​drewnoakes</code></a> with
<a href="https://github.com/Copilot"><code>@​Copilot</code></a> in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/722">drewnoakes/metadata-extractor#722</a></li>
<li>Normalize sign to numerator in Rational.toString() by <a
href="https://github.com/drewnoakes"><code>@​drewnoakes</code></a> with
<a href="https://github.com/Copilot"><code>@​Copilot</code></a> in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/724">drewnoakes/metadata-extractor#724</a></li>
<li>Extract IPTC metadata from PNG text chunks by <a
href="https://github.com/drewnoakes"><code>@​drewnoakes</code></a> with
<a href="https://github.com/Copilot"><code>@​Copilot</code></a> in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/723">drewnoakes/metadata-extractor#723</a></li>
<li>Tolerate duplicate PNG chunks that are not allowed to appear
multiple times by <a
href="https://github.com/drewnoakes"><code>@​drewnoakes</code></a> with
<a href="https://github.com/Copilot"><code>@​Copilot</code></a> in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/725">drewnoakes/metadata-extractor#725</a></li>
<li>Null-check and length-guard getPowerUpTimeDescription by <a
href="https://github.com/drewnoakes"><code>@​drewnoakes</code></a> with
<a href="https://github.com/Copilot"><code>@​Copilot</code></a> in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/727">drewnoakes/metadata-extractor#727</a></li>
<li>Move DisposalMethod description logic to GifControlDescriptor by <a
href="https://github.com/drewnoakes"><code>@​drewnoakes</code></a> with
<a href="https://github.com/Copilot"><code>@​Copilot</code></a> in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/729">drewnoakes/metadata-extractor#729</a></li>
<li>Port .NET RiffReader.processChunks error checking to Java by <a
href="https://github.com/drewnoakes"><code>@​drewnoakes</code></a> with
<a href="https://github.com/Copilot"><code>@​Copilot</code></a> in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/728">drewnoakes/metadata-extractor#728</a></li>
<li>Fix NPE in ItemLocationBox when offset_size/length_size is 0 (valid
AVIF) by <a href="https://github.com/hanskr"><code>@​hanskr</code></a>
in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/732">drewnoakes/metadata-extractor#732</a></li>
<li>Harden ICO parsing against oversized image counts by <a
href="https://github.com/drewnoakes"><code>@​drewnoakes</code></a> with
<a href="https://github.com/Copilot"><code>@​Copilot</code></a> in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/734">drewnoakes/metadata-extractor#734</a></li>
<li>Support LONG8 and SLONG8 TIFF data formats by <a
href="https://github.com/dschmidt"><code>@​dschmidt</code></a> in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/739">drewnoakes/metadata-extractor#739</a></li>
<li>Detect QuickTime files that lack a leading ftyp box by <a
href="https://github.com/dschmidt"><code>@​dschmidt</code></a> in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/736">drewnoakes/metadata-extractor#736</a></li>
<li>Add basic MP4 udta metadata (Title, Comment, Subtitle, Rating,
Category, Mood) by <a
href="https://github.com/drewnoakes"><code>@​drewnoakes</code></a> in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/740">drewnoakes/metadata-extractor#740</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/drewnoakes"><code>@​drewnoakes</code></a> with
<a href="https://github.com/Copilot"><code>@​Copilot</code></a> made
their first contribution in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/721">drewnoakes/metadata-extractor#721</a></li>
<li><a href="https://github.com/hanskr"><code>@​hanskr</code></a> made
their first contribution in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/732">drewnoakes/metadata-extractor#732</a></li>
<li><a href="https://github.com/dschmidt"><code>@​dschmidt</code></a>
made their first contribution in <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/pull/739">drewnoakes/metadata-extractor#739</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/drewnoakes/metadata-extractor/compare/2.20.0...2.20.1">https://github.com/drewnoakes/metadata-extractor/compare/2.20.0...2.20.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/drewnoakes/metadata-extractor/commit/6f037943c0811799a59b0ea19896cb2b07da17be"><code>6f03794</code></a>
Merge pull request <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/issues/740">#740</a>
from drewnoakes/dev/drnoakes/mp4-metadata-pr-review</li>
<li><a
href="https://github.com/drewnoakes/metadata-extractor/commit/d01cc419eea457b8d482f864d1f1bbacee834828"><code>d01cc41</code></a>
Detect QuickTime files that lack a leading ftyp box (<a
href="https://redirect.github.com/drewnoakes/metadata-extractor/issues/736">#736</a>)</li>
<li><a
href="https://github.com/drewnoakes/metadata-extractor/commit/1b45d70723192a02bde7626d6e011081d52c3592"><code>1b45d70</code></a>
Support LONG8 and SLONG8 TIFF data formats</li>
<li><a
href="https://github.com/drewnoakes/metadata-extractor/commit/353e27c37c414702e92304a4067de96bb383def1"><code>353e27c</code></a>
Address Copilot review: align entry parsing and fix tag name casing</li>
<li><a
href="https://github.com/drewnoakes/metadata-extractor/commit/31632d2d70497333e5aa71401323f17e0f6d515a"><code>31632d2</code></a>
Clean up salvaged MP4 udta metadata code</li>
<li><a
href="https://github.com/drewnoakes/metadata-extractor/commit/845cdbab9a8f1348c52b1b4e10a6f774929ba303"><code>845cdba</code></a>
Add more UDTA metadata to MP4 parser (Title, Comment, Subtitle, Rating,
Categ...</li>
<li><a
href="https://github.com/drewnoakes/metadata-extractor/commit/5647ffd0541279bc488f6387ea91aa3c785de36b"><code>5647ffd</code></a>
Harden ICO parsing against oversized image counts (<a
href="https://redirect.github.com/drewnoakes/metadata-extractor/issues/734">#734</a>)</li>
<li><a
href="https://github.com/drewnoakes/metadata-extractor/commit/99383f6d345dfa9a72e0780c37876a8ebd31ce31"><code>99383f6</code></a>
Merge pull request <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/issues/732">#732</a>
from hanskr/fix-iloc-zero-offset-size-npe</li>
<li><a
href="https://github.com/drewnoakes/metadata-extractor/commit/35af34ae39dec240e1e0a1bded0161f6f86df38e"><code>35af34a</code></a>
Fix NPE in ItemLocationBox when offset_size/length_size is 0</li>
<li><a
href="https://github.com/drewnoakes/metadata-extractor/commit/520e07fed8167863e2245f67d2622f9bb473d017"><code>520e07f</code></a>
Merge pull request <a
href="https://redirect.github.com/drewnoakes/metadata-extractor/issues/728">#728</a>
from drewnoakes/copilot/enhance-error-checking-riffre...</li>
<li>Additional commits viewable in <a
href="https://github.com/drewnoakes/metadata-extractor/compare/2.20.0...2.21.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=com.drewnoakes:metadata-extractor&package-manager=gradle&previous-version=2.20.0&new-version=2.21.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-08 21:43:28 +01:00
dependabot[bot] 606f493597 build(deps): bump docker/login-action from 4.5.1 to 4.6.0 (#7341)
Bumps [docker/login-action](https://github.com/docker/login-action) from
4.5.1 to 4.6.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/docker/login-action/releases">docker/login-action's
releases</a>.</em></p>
<blockquote>
<h2>v4.6.0</h2>
<ul>
<li>Harden buildx scoped config path handling by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/login-action/pull/1059">docker/login-action#1059</a></li>
<li>Bump <code>@​aws-sdk/client-ecr</code> and
<code>@​aws-sdk/client-ecr-public</code> to 3.1095.0 in <a
href="https://redirect.github.com/docker/login-action/pull/1051">docker/login-action#1051</a></li>
<li>Bump js-yaml from 5.2.1 to 5.2.2 in <a
href="https://redirect.github.com/docker/login-action/pull/1057">docker/login-action#1057</a></li>
<li>Bump postcss from 8.5.10 to 8.5.22 in <a
href="https://redirect.github.com/docker/login-action/pull/1056">docker/login-action#1056</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/login-action/compare/v4.5.2...v4.6.0">https://github.com/docker/login-action/compare/v4.5.2...v4.6.0</a></p>
<h2>v4.5.2</h2>
<ul>
<li>Surface Docker Hub OIDC error responses by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/login-action/pull/1058">docker/login-action#1058</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/login-action/compare/v4.5.1...v4.5.2">https://github.com/docker/login-action/compare/v4.5.1...v4.5.2</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/docker/login-action/commit/dbcb813823bdd20940b903addbd779551569679f"><code>dbcb813</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/1051">#1051</a>
from docker/dependabot/npm_and_yarn/aws-sdk-dependen...</li>
<li><a
href="https://github.com/docker/login-action/commit/5bcb015ee6ec720ecdeaef2dc1164122e9b209fc"><code>5bcb015</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/login-action/commit/b30b2f2d3196c1714318ba0c3c3bec211d949752"><code>b30b2f2</code></a>
build(deps): bump the aws-sdk-dependencies group across 1 directory with
2 up...</li>
<li><a
href="https://github.com/docker/login-action/commit/9087f1e6d666fe0292409e3c819680c18526e108"><code>9087f1e</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/1057">#1057</a>
from docker/dependabot/npm_and_yarn/js-yaml-5.2.2</li>
<li><a
href="https://github.com/docker/login-action/commit/0009830ea169ca16c24c0ea4cac1c325bfa3aee4"><code>0009830</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/login-action/commit/23255232d3e43c8f0052d9a0dba82a515a88ce92"><code>2325523</code></a>
build(deps): bump js-yaml from 5.2.1 to 5.2.2</li>
<li><a
href="https://github.com/docker/login-action/commit/4ec1d4a769e8b05a89a7396551dc38b329211688"><code>4ec1d4a</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/1056">#1056</a>
from docker/dependabot/npm_and_yarn/postcss-8.5.22</li>
<li><a
href="https://github.com/docker/login-action/commit/5fc99ba47bca274c5a499688f71c7ea79c0ea1b3"><code>5fc99ba</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/1053">#1053</a>
from docker/dependabot/github_actions/aws-actions/co...</li>
<li><a
href="https://github.com/docker/login-action/commit/e512bd59d16c53d79ea5c0f0e345fe554453c4bb"><code>e512bd5</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/1052">#1052</a>
from docker/dependabot/github_actions/codeql-actions...</li>
<li><a
href="https://github.com/docker/login-action/commit/a146c91b8f371700d323bae808af7cbdc2766ed5"><code>a146c91</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/1059">#1059</a>
from crazy-max/harden-buildx-scope-paths</li>
<li>Additional commits viewable in <a
href="https://github.com/docker/login-action/compare/abd2ef45e78c5afb21d64d4ca52ee8550d9572c7...dbcb813823bdd20940b903addbd779551569679f">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=docker/login-action&package-manager=github_actions&previous-version=4.5.1&new-version=4.6.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-08 21:43:11 +01:00
dependabot[bot] 7552fcc934 build(deps): bump ossf/scorecard-action from 2.4.3 to 2.4.4 (#7342)
Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action)
from 2.4.3 to 2.4.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/ossf/scorecard-action/releases">ossf/scorecard-action's
releases</a>.</em></p>
<blockquote>
<h2>v2.4.4</h2>
<h2>What's Changed</h2>
<p>This update bumps the Scorecard version to the v5.5.0 release. For a
complete list of changes, please refer to the <a
href="https://github.com/ossf/scorecard/releases/tag/v5.4.0">Scorecard
v5.4.0 release notes</a> and the <a
href="https://github.com/ossf/scorecard/releases/tag/v5.5.0">Scorecard
v5.5.0 release notes</a>.</p>
<ul>
<li>log POST failures instead of failing entire action by <a
href="https://github.com/spencerschrock"><code>@​spencerschrock</code></a>
in <a
href="https://redirect.github.com/ossf/scorecard-action/pull/1625">ossf/scorecard-action#1625</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/ossf/scorecard-action/compare/v2.4.3...v2.4.4">https://github.com/ossf/scorecard-action/compare/v2.4.3...v2.4.4</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/ossf/scorecard-action/commit/2d1146689b8cda280b9bc96326124645441f03bc"><code>2d11466</code></a>
Bump action tag for v2.4.4 release (<a
href="https://redirect.github.com/ossf/scorecard-action/issues/1688">#1688</a>)</li>
<li><a
href="https://github.com/ossf/scorecard-action/commit/1bd3285473b114fb77ed934c4ba0aea31aa0f866"><code>1bd3285</code></a>
🌱 Bump the docker-images group across 1 directory with 2
updates (<a
href="https://redirect.github.com/ossf/scorecard-action/issues/1">#1</a>...</li>
<li><a
href="https://github.com/ossf/scorecard-action/commit/913edce4c1ce57261797e2ddcb74e493d9ce9700"><code>913edce</code></a>
🌱 Bump github.com/containerd/containerd from 1.7.32 to 1.7.33
(<a
href="https://redirect.github.com/ossf/scorecard-action/issues/1671">#1671</a>)</li>
<li><a
href="https://github.com/ossf/scorecard-action/commit/0957b8f1c327cafd868bd6bdb7e441c016628783"><code>0957b8f</code></a>
🌱 Bump golang.org/x/net from 0.56.0 to 0.57.0 (<a
href="https://redirect.github.com/ossf/scorecard-action/issues/1680">#1680</a>)</li>
<li><a
href="https://github.com/ossf/scorecard-action/commit/f0061eb3ff8c4d311e47276c8bcc96e96ed5dc32"><code>f0061eb</code></a>
🌱 Bump google.golang.org/grpc from 1.81.1 to 1.82.1 (<a
href="https://redirect.github.com/ossf/scorecard-action/issues/1687">#1687</a>)</li>
<li><a
href="https://github.com/ossf/scorecard-action/commit/20ee7324026c52f8d0c4b372a7bf382a01b72ff9"><code>20ee732</code></a>
🌱 Bump github.com/sigstore/cosign/v2 from 2.6.3 to 2.6.4 (<a
href="https://redirect.github.com/ossf/scorecard-action/issues/1685">#1685</a>)</li>
<li><a
href="https://github.com/ossf/scorecard-action/commit/9f295ef01b1f77f15b1647c790db825d9577a441"><code>9f295ef</code></a>
🌱 Bump the github-actions group with 6 updates (<a
href="https://redirect.github.com/ossf/scorecard-action/issues/1686">#1686</a>)</li>
<li><a
href="https://github.com/ossf/scorecard-action/commit/69bf556cea38c0fbe034b2ce923253eca7c4d651"><code>69bf556</code></a>
🌱 Bump github.com/sigstore/sigstore-go from 1.1.4 to 1.2.0 (<a
href="https://redirect.github.com/ossf/scorecard-action/issues/1681">#1681</a>)</li>
<li><a
href="https://github.com/ossf/scorecard-action/commit/94e8b9600123b21167ebf56077904fc6ca421a95"><code>94e8b96</code></a>
🌱 Bump github.com/sigstore/rekor from 1.5.0 to 1.5.2 (<a
href="https://redirect.github.com/ossf/scorecard-action/issues/1673">#1673</a>)</li>
<li><a
href="https://github.com/ossf/scorecard-action/commit/c7a1b37bbc88c32d53056d9071ce2ba0df381dfb"><code>c7a1b37</code></a>
🌱 Bump github.com/sigstore/fulcio from 1.8.5 to 1.8.6 (<a
href="https://redirect.github.com/ossf/scorecard-action/issues/1675">#1675</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/ossf/scorecard-action/compare/4eaacf0543bb3f2c246792bd56e8cdeffafb205a...2d1146689b8cda280b9bc96326124645441f03bc">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ossf/scorecard-action&package-manager=github_actions&previous-version=2.4.3&new-version=2.4.4)](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-08 21:42:54 +01:00
James Brunton 37a48aa7a7 Replace ESLint and dpdm with Oxlint (#7330)
# Description of Changes
Smaller scope than #6689 to try and get this finished.

Replace ESLint and dpdm with Oxlint, a TS linter written in Rust so its
performance is dramatically better than the existing tools we use.

## Speed improvement
- Current ESLint run: 13.76s
- Current dpdm run: 3.59s
- Total time: 17.35s
- New Oxlint run: 0.90s

So Oxlint is about a 20x speed improvement.

## Differences
When I last tried to do this, we could recreate our rules identically
with Oxlint, but that's not true any more. Oxlint has no current
equivalent for ESLint's `no-restricted-syntax` rule, which we were using
to ban usages of `<button>` and stuff in specific components to try and
encourage them to use our shared UI. This is a very recent addition to
our linting config, and personally I'm willing to drop it for now at
least. We can still ban specific imports in files, so the files which we
were trying to enforce shared UI will still ban directly importing
Mantine, so that'll probably be most of the cases still caught, but I
think there are other ways we can encourage using the shared UI beyond
just using the linter for it.

I did try building a custom TS rule for it and it only slowed it down a
tiny bit (it took 1.1s) but it had to be built on an unreleased alpha
API which just sounds like a maintenance headache we don't need to deal
with for a rule that we don't really need.
2026-08-07 16:02:11 +00:00
James Brunton 408f9ef148 Fix any type usages in frontend code (#7326)
# Description of Changes
Continued effort towards removing all uses of the `any` type in our
frontend code. This PR fixes 10 more folders and removes them from the
exclude list. All of them were really simple fixes.
2026-08-07 13:49:19 +00:00
EthanHealy01andAnthony Stirling cff6549a40 fix(frontend): keep file persistence working when IndexedDB refuses blobs (#7314)
# Description of Changes

Fixes the WebKit nightly failures ([run
31067620195](https://github.com/Stirling-Tools/Stirling-PDF/actions/runs/31067620195/attempts/1)):
8 tests failed on `stubbed-webkit` only, and every one of them logs the
same thing in its trace:

```
IndexedDB add error: UnknownError: Error preparing Blob/File data to be stored in object store
```

## What broke

`storeStirlingFile` stores the `File` itself in IndexedDB, so multi-GB
uploads are persisted by reference and never materialize in JS memory.
That came in with #7175 (`data: stirlingFile` replacing `data: await
stirlingFile.arrayBuffer()`), which is a real memory win and worth
keeping.

WebKit refuses blob values whenever it can't write the blob's backing
file, and rejects the request with the error above. The rejection was
only `console.error`d, so on WebKit **no upload ever persisted**, and
everything that reads the bytes back behaved as if the upload never
happened:

- `file-state-across-tools` — file gone after navigating; the sidebar
shows "No files yet"
- `compare` — `FileSelectorPicker: upload failed`, so the slot stays
`data-slot-state="empty"`
- `classification-grouping` / `classification-heuristic-upload` — the
label backfill and thumbnails read from IDB (`not in IndexedDB (likely
remote-only stub)`), so files land in "Recent" with no category headers

Chromium and Firefox store blobs fine, and PR CI only runs the `stubbed`
(chromium) project, so nightly was the only gate that could catch it.

## The fix

Try the blob first, keep a fallback:

- `storeStirlingFile`'s `add` is extracted into `addFileRecord` so it
can run twice
- if the value was a Blob and the failure is `UnknownError` /
`DataCloneError`, re-add the record with an `ArrayBuffer` copy and set
`blobValuesSupported = false`, so later files in that session go
straight to the copy path instead of losing the blob attempt every time
- deliberately narrow: `QuotaExceededError` and `ConstraintError` still
propagate, because a copy would fail the same way and retrying would
hide the real cause
- dropped two internal `console.error`s: every caller already reports
(`addFiles`, `FileSelectorPicker`, `zipFileService` collects into
`result.errors`), so they were duplicate noise

Every writer goes through `storeStirlingFile` (uploads, the file picker,
zip extraction, folder automation, `IndexedDBContext`), so this one seam
covers all of them. The read paths already accept either shape (`new
Blob([record.data], ...)`).

Net effect: Chromium and Firefox keep the no-copy path; engines that
refuse blobs degrade to the pre-#7175 behaviour instead of silently
losing files. On such an engine a very large file can still exhaust
renderer memory — the fallback warns about exactly that. Fixing that
properly means chunked storage, which is out of scope here.

## Verification

Reproduced and confirmed the cause by A/B on a branch that predates
#7175: as-is 8/8 pass on WebKit, and applying only #7175's `data:
stirlingFile` line reproduces the exact CI failure set.

| Check | Result |
|---|---|
| `stubbed-webkit`: the 8 nightly failures +
`classification-heuristic-upload` | 9 passed |
| `stubbed-webkit`: `files-page`, `page-editor-rotation`,
`encrypted-pdf-unlock` | 32 passed, 1 skipped |
| `stubbed` (chromium): the same specs + `files-page` | 35 passed, 1
skipped |
| Frontend unit suite | 210 files, 1797 passed |
| `typecheck:core`, `typecheck:proprietary`, eslint, prettier | clean |

New unit coverage in `fileStorage.blobFallback.test.ts` pins the
contract over `fake-indexeddb` with `add` instrumented to count blob vs
copy attempts: blob path when accepted, blob-then-copy when refused (and
readable back), one attempt only for later files, and quota not retried.

---

## 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] Frontend typecheck (core + proprietary), eslint, prettier, the
unit suite, and the affected Playwright specs on chromium and webkit all
pass

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2026-08-07 14:36:46 +01:00
dependabot[bot] 1867c8f285 build(deps): bump js-yaml from 4.2.0 to 4.3.1 in /frontend (#7322)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.2.0 to 4.3.1.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/nodeca/js-yaml/blob/4.3.1/CHANGELOG.md">js-yaml's
changelog</a>.</em></p>
<blockquote>
<h2>4.3.1 - 2026-07-31</h2>
<h3>Security</h3>
<ul>
<li>[backport] Remove quadratic complexity from <code>!!omap</code>
duplicate key detection.</li>
</ul>
<h2>4.3.0 - 2026-06-27</h2>
<h3>Added</h3>
<ul>
<li>[backport] Added <code>maxTotalMergeKeys</code> (10000) loader
option to limit the total number of
keys processed by YAML merge (<code>&lt;&lt;</code>) across one
<code>load()</code> / <code>loadAll()</code> call.</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Restore umd builds back to es5.</li>
</ul>
<h3>Removed</h3>
<ul>
<li>[backport] <code>maxMergeSeqLength</code> replaced with
<code>maxTotalMergeKeys</code> for limiting YAML merge
processing.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/nodeca/js-yaml/commit/86e91b815b8794c3c73a179c1770871e37ec2df8"><code>86e91b8</code></a>
4.3.1 released</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/c3cc4b0bb9ddb9af2dd9b61e0d56f5ce7983cd4a"><code>c3cc4b0</code></a>
Backport quadratic complexity fix for !!omap</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/33d05b5d29a8c21360f620f7e1c1706e24522eda"><code>33d05b5</code></a>
4.3.0 released</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/663bfab6db2b4a146a9366fd685f069345be4ddb"><code>663bfab</code></a>
Drop demo publish, to not override new v5 one.</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/1cb8c7b94bf75e15116869c1c0482dcb22785986"><code>1cb8c7b</code></a>
Add v4-legacy tag for publish</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/02f27afad532763263cd2b6be35c24ee8e1f6157"><code>02f27af</code></a>
Restore umd builds back to es5</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/8be84edaf15e7c394fa3b813179d1bcc280e87fb"><code>8be84ed</code></a>
Fix es5 compatibility</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/59423c6f8cdc78742ac00e25a4dd39ef16b702e4"><code>59423c6</code></a>
Replace <code>maxMergeSeqLength</code> option with
<code>maxTotalMergeKeys</code> (more robust). Ba...</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/6842ef6a02df01ca7282ea01dc3c70787710c05d"><code>6842ef6</code></a>
doc polish</li>
<li>See full diff in <a
href="https://github.com/nodeca/js-yaml/compare/4.2.0...4.3.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=js-yaml&package-manager=npm_and_yarn&previous-version=4.2.0&new-version=4.3.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)
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-07 14:01:11 +01:00
dependabot[bot] 2c74b5bf81 build(deps): bump js-yaml from 4.2.0 to 4.3.1 in /devTools (#7323)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.2.0 to 4.3.1.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/nodeca/js-yaml/blob/4.3.1/CHANGELOG.md">js-yaml's
changelog</a>.</em></p>
<blockquote>
<h2>4.3.1 - 2026-07-31</h2>
<h3>Security</h3>
<ul>
<li>[backport] Remove quadratic complexity from <code>!!omap</code>
duplicate key detection.</li>
</ul>
<h2>4.3.0 - 2026-06-27</h2>
<h3>Added</h3>
<ul>
<li>[backport] Added <code>maxTotalMergeKeys</code> (10000) loader
option to limit the total number of
keys processed by YAML merge (<code>&lt;&lt;</code>) across one
<code>load()</code> / <code>loadAll()</code> call.</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Restore umd builds back to es5.</li>
</ul>
<h3>Removed</h3>
<ul>
<li>[backport] <code>maxMergeSeqLength</code> replaced with
<code>maxTotalMergeKeys</code> for limiting YAML merge
processing.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/nodeca/js-yaml/commit/86e91b815b8794c3c73a179c1770871e37ec2df8"><code>86e91b8</code></a>
4.3.1 released</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/c3cc4b0bb9ddb9af2dd9b61e0d56f5ce7983cd4a"><code>c3cc4b0</code></a>
Backport quadratic complexity fix for !!omap</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/33d05b5d29a8c21360f620f7e1c1706e24522eda"><code>33d05b5</code></a>
4.3.0 released</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/663bfab6db2b4a146a9366fd685f069345be4ddb"><code>663bfab</code></a>
Drop demo publish, to not override new v5 one.</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/1cb8c7b94bf75e15116869c1c0482dcb22785986"><code>1cb8c7b</code></a>
Add v4-legacy tag for publish</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/02f27afad532763263cd2b6be35c24ee8e1f6157"><code>02f27af</code></a>
Restore umd builds back to es5</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/8be84edaf15e7c394fa3b813179d1bcc280e87fb"><code>8be84ed</code></a>
Fix es5 compatibility</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/59423c6f8cdc78742ac00e25a4dd39ef16b702e4"><code>59423c6</code></a>
Replace <code>maxMergeSeqLength</code> option with
<code>maxTotalMergeKeys</code> (more robust). Ba...</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/6842ef6a02df01ca7282ea01dc3c70787710c05d"><code>6842ef6</code></a>
doc polish</li>
<li>See full diff in <a
href="https://github.com/nodeca/js-yaml/compare/4.2.0...4.3.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=js-yaml&package-manager=npm_and_yarn&previous-version=4.2.0&new-version=4.3.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)
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-07 14:01:07 +01:00
Anthony Stirling 82e1bd62a2 Move CODEOWNERS to review teams (#7325)
# Description of Changes

CODEOWNERS now points at review teams (`maintainers`,
`backend-reviewers`, `frontend-reviewers`, `devops-reviewers`, `all`)
instead of individual usernames, so membership is managed in the org
rather than in this file. Ludy87 and balazs-szucs stay listed by hand
since outside collaborators cannot be team members. including the deploy
and demo-comment allowlists.

---

## Checklist

### General

- [x] 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)
- [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)

- [ ] 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-07 12:27:23 +00:00
Matheus SaitoandAnthony Stirling fa11034a99 Added Measurement Scale Support for Architectural Drawings (#6121) (#6215)
Fix #6121 
# Description of Changes

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

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

Closes (#6121 )
-->
This PR expands the viewer ruler/measurement tool with real-world scale
support. Users can now apply preset scales, define custom scales,
calibrate a scale by drawing a reference measurement and entering its
known real-world distance, and view measurements with scaled real-world
values.

It also refactors PDF `/Measure` and `/VP` scale extraction out of
`EmbedPdfViewer` into a dedicated utility, centralizes ruler state
management in a dedicated hook, persists ruler measurements and selected
scales per file during the browser session, remembers the last
calibration unit locally, and updates the ruler overlay so measurements
remain aligned with the PDF page during rotation and scrolling.

**New Files**

- `RulerMeasurementLayer.tsx` - Renders ruler measurements in the SVG
overlay, including lines, points, labels, page/scaled values, delete
controls, live previews, clear controls, and label visibility modes.
    
- `RulerScaleSettingsButton.tsx` - Adds the scale settings
button/popover to the viewer toolbar.
    
- `ScaleCalibrationDialog.tsx` - Provides the calibration modal where
users enter a known real-world distance to calculate the scale
automatically.
    
- `ScaleSettingsPanel.tsx` - Provides preset scales, custom scale input,
calibration entry point, active scale display, and reset controls.
    
- `useMeasurementManager.ts` - Centralizes ruler state, custom scale
state, calibration flow, per-file measurements, session persistence, and
loading of PDF-derived scale data.
    
- `measurementPreferences.ts` - Persists the last calibration unit in
`localStorage`.
    
- `measurementTypes.ts` - Defines shared measurement, point, scale, page
scale, and viewport scale types.
    
- `measurementUtils.ts` - Provides unit conversion, scale calculation,
validation, formatting, calibration helpers, and session storage
helpers.
    
- `measurementUtils.test.ts` - Adds unit tests for scale calculations,
unit conversion, preset parsing, ratio derivation, and calibration.
    
- `pdfMeasurementExtraction.ts` - Moves PDF `/Measure` and `/VP` scale
extraction into a dedicated utility.
    

**Changed Files**

- `EmbedPdfViewer.tsx` - Removes inline PDF scale extraction and
delegates ruler/measurement state to `useMeasurementManager`; integrates
the ruler overlay, custom scale support, restored measurements, and
calibration dialog.
    
- `LocalEmbedPDF.tsx` - Adds page-level metadata used by the ruler
overlay, including page width, height, and native page rotation.
    
- `RotateAPIBridge.tsx` - Adds immediate rotation update propagation so
ruler measurements can update their page-anchored positions during
rotation changes.
    
- `RulerOverlay.tsx` - Refactors the ruler overlay to use shared
measurement types/utilities, support custom scales, calibration
measurements, restored measurements, measurement change listeners,
rotation-aware positioning, and scroll compensation, also holding Alt
key will activate pass-through behavior so labels do not block ruler
interactions.
    
- `useViewerWorkbenchBarButtons.tsx` - Adds the ruler scale settings
action and coordinates ruler, pan mode, and calibration behavior.
    
- `ViewerContext.tsx` - Adds immediate rotation notification support
used by ruler measurements while viewer rotation changes are applied.
    
- `en-GB/translation.toml` and `en-US/translation.toml` - Add UI text
for scale settings, calibration actions, ruler measurement values, and
ruler label controls.


---

## 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)
- [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)

Current scale panel : 

<img width="1316" height="515" alt="Captura de tela de 2026-05-31
21-57-53"
src="https://github.com/user-attachments/assets/df0ffb7d-4546-450c-8380-409658852a87"
/>

Current calibration input : 

<img width="1021" height="522" alt="Captura de tela de 2026-05-31
21-59-52"
src="https://github.com/user-attachments/assets/e17c14e7-fec7-42e9-af48-33ef825acb6c"
/>

Example of usage : 

<img width="1316" height="760" alt="Captura de tela de 2026-05-31
22-31-01"
src="https://github.com/user-attachments/assets/fcd3578e-1eaa-42d3-b30b-3fc87c796f90"
/>


### 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-07 09:53:33 +00:00
kastenherriandAnthony Stirling 84b0b69fe7 Frontend: de-DE: fix inconsistent translation (#6536)
# Description of Changes
Change description text for hotkey settings in the german locale to
"Kürzel ändern" to match the change button.

before:
<img width="731" height="246" alt="stirling-inconsistency-shortcut"
src="https://github.com/user-attachments/assets/a0e4bcb9-d2b6-4759-a7cd-75632176eebd"
/>
 
after:
<img width="776" height="254" alt="hotkey-inconsistency-fix"
src="https://github.com/user-attachments/assets/e0da4d26-f923-4e15-922f-e0e5e21f062f"
/>

Closes #6535 

<!--
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)

- [x] 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)

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2026-08-07 09:29:13 +00:00
James Brunton 18516b88ff Fix hidden circular dependency in watched folders code (#7308)
# Description of Changes
The restructuring of the frontend PR (#7062) has highlighted a couple of
issues with the source which are currently hidden due to the structure
of the code. This PR fixes the issues in Watched Folders:
- There's a circular dependency in TS code
- The `.gitignore` file excludes the watched folders dir by accident
2026-08-07 09:00:18 +00:00
dependabot[bot] 5ef93ac1e8 build(deps): bump docker/metadata-action from 6.1.0 to 6.2.0 (#7307)
Bumps
[docker/metadata-action](https://github.com/docker/metadata-action) from
6.1.0 to 6.2.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/docker/metadata-action/releases">docker/metadata-action's
releases</a>.</em></p>
<blockquote>
<h2>v6.2.0</h2>
<ul>
<li>Preserve names in esbuild bundle by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/metadata-action/pull/689">docker/metadata-action#689</a></li>
<li>Bump <code>@​actions/core</code> from 3.0.0 to 3.0.1 in <a
href="https://redirect.github.com/docker/metadata-action/pull/663">docker/metadata-action#663</a></li>
<li>Bump <code>@​actions/github</code> from 9.0.0 to 9.1.1 in <a
href="https://redirect.github.com/docker/metadata-action/pull/666">docker/metadata-action#666</a></li>
<li>Bump <code>@​docker/actions-toolkit</code> from 0.90.0 to 0.92.0 in
<a
href="https://redirect.github.com/docker/metadata-action/pull/672">docker/metadata-action#672</a>
<a
href="https://redirect.github.com/docker/metadata-action/pull/696">docker/metadata-action#696</a></li>
<li>Bump <code>@​sigstore/core</code> from 3.1.0 to 3.2.1 in <a
href="https://redirect.github.com/docker/metadata-action/pull/690">docker/metadata-action#690</a></li>
<li>Bump <code>@​sigstore/verify</code> from 3.1.0 to 3.1.1 in <a
href="https://redirect.github.com/docker/metadata-action/pull/693">docker/metadata-action#693</a></li>
<li>Bump csv-parse from 6.2.1 to 7.0.0 in <a
href="https://redirect.github.com/docker/metadata-action/pull/683">docker/metadata-action#683</a></li>
<li>Bump js-yaml from 4.1.1 to 4.3.0 in <a
href="https://redirect.github.com/docker/metadata-action/pull/688">docker/metadata-action#688</a></li>
<li>Bump moment-timezone from 0.6.1 to 0.6.2 in <a
href="https://redirect.github.com/docker/metadata-action/pull/664">docker/metadata-action#664</a></li>
<li>Bump semver from 7.7.4 to 7.8.5 in <a
href="https://redirect.github.com/docker/metadata-action/pull/665">docker/metadata-action#665</a>
<a
href="https://redirect.github.com/docker/metadata-action/pull/695">docker/metadata-action#695</a></li>
<li>Bump sigstore from 4.1.0 to 4.1.1 in <a
href="https://redirect.github.com/docker/metadata-action/pull/694">docker/metadata-action#694</a></li>
<li>Bump tmp from 0.2.5 to 0.2.7 in <a
href="https://redirect.github.com/docker/metadata-action/pull/673">docker/metadata-action#673</a></li>
<li>Bump undici from 6.25.0 to 6.27.0 in <a
href="https://redirect.github.com/docker/metadata-action/pull/686">docker/metadata-action#686</a></li>
<li>Bump vite from 7.3.2 to 7.3.6 in <a
href="https://redirect.github.com/docker/metadata-action/pull/685">docker/metadata-action#685</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/metadata-action/compare/v6.1.0...v6.2.0">https://github.com/docker/metadata-action/compare/v6.1.0...v6.2.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/docker/metadata-action/commit/dc802804100637a589fabce1cb79ff13a1411302"><code>dc80280</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/metadata-action/issues/696">#696</a>
from docker/dependabot/npm_and_yarn/docker/actions-to...</li>
<li><a
href="https://github.com/docker/metadata-action/commit/2b9fe830efc58cb7b0fab7c94300b1afa01864db"><code>2b9fe83</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/metadata-action/commit/8128ce30ab370fd360c8423698dcc077f2db8087"><code>8128ce3</code></a>
chore(deps): Bump <code>@​docker/actions-toolkit</code> from 0.91.0 to
0.92.0</li>
<li><a
href="https://github.com/docker/metadata-action/commit/1d1c89551edb081628e818368680beffb2bbccee"><code>1d1c895</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/metadata-action/issues/695">#695</a>
from docker/dependabot/npm_and_yarn/semver-7.8.5</li>
<li><a
href="https://github.com/docker/metadata-action/commit/7f0c2dd4c83ea6258f78bf2fc6a02908417518ad"><code>7f0c2dd</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/metadata-action/issues/694">#694</a>
from docker/dependabot/npm_and_yarn/sigstore-4.1.1</li>
<li><a
href="https://github.com/docker/metadata-action/commit/025f8c5c8167edab853571a387d8e80ccc1e91ed"><code>025f8c5</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/metadata-action/commit/e98d63c3081990566e174100a3afaa97aefbbcf3"><code>e98d63c</code></a>
chore(deps): Bump semver from 7.8.1 to 7.8.5</li>
<li><a
href="https://github.com/docker/metadata-action/commit/37d93799507b94cf1c1885efd7278d52a0fb806a"><code>37d9379</code></a>
chore(deps): Bump sigstore from 4.1.0 to 4.1.1</li>
<li><a
href="https://github.com/docker/metadata-action/commit/a1b80728476a956a25c5eeaff91c4dc46e7ac1bc"><code>a1b8072</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/metadata-action/issues/690">#690</a>
from docker/dependabot/npm_and_yarn/sigstore/core-3.2.1</li>
<li><a
href="https://github.com/docker/metadata-action/commit/e0e338106d6ae0da5b44537049a34620b3559a5c"><code>e0e3381</code></a>
[dependabot skip] chore: update generated content</li>
<li>Additional commits viewable in <a
href="https://github.com/docker/metadata-action/compare/80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9...dc802804100637a589fabce1cb79ff13a1411302">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=docker/metadata-action&package-manager=github_actions&previous-version=6.1.0&new-version=6.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-07 08:53:57 +00:00
dependabot[bot] be7aee3ef2 build(deps): bump actions/setup-python from 6.2.0 to 7.0.0 (#7294)
Bumps [actions/setup-python](https://github.com/actions/setup-python)
from 6.2.0 to 7.0.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/setup-python/releases">actions/setup-python's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.0</h2>
<h2>What's Changed</h2>
<h3>Enhancements</h3>
<ul>
<li>Migrate to ESM and upgrade dependencies by <a
href="https://github.com/priyagupta108"><code>@​priyagupta108</code></a>
in <a
href="https://redirect.github.com/actions/setup-python/pull/1330">actions/setup-python#1330</a></li>
<li>Pin SHA commits and update docs with latest versions by <a
href="https://github.com/HarithaVattikuti"><code>@​HarithaVattikuti</code></a>
in <a
href="https://redirect.github.com/actions/setup-python/pull/1338">actions/setup-python#1338</a></li>
<li>Remove the pip-install input by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a> in
<a
href="https://redirect.github.com/actions/setup-python/pull/1336">actions/setup-python#1336</a></li>
</ul>
<h3>Bug Fix</h3>
<ul>
<li>Fix to Classify stderr warning messages as warnings instead of
errors in annotations by <a
href="https://github.com/lmvysakh"><code>@​lmvysakh</code></a> in <a
href="https://redirect.github.com/actions/setup-python/pull/1335">actions/setup-python#1335</a></li>
<li>Validate and retry manifest fetch to prevent silent failures by <a
href="https://github.com/priyagupta108"><code>@​priyagupta108</code></a>
in <a
href="https://redirect.github.com/actions/setup-python/pull/1332">actions/setup-python#1332</a></li>
</ul>
<h3>Dependency Upgrade</h3>
<ul>
<li>Bump certifi from 2020.6.20 to 2024.7.4 in
/<strong>tests</strong>/data by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/setup-python/pull/1328">actions/setup-python#1328</a></li>
<li>Remove EOL Python versions and Bumps numpy text fixture by <a
href="https://github.com/priya-kinthali"><code>@​priya-kinthali</code></a>
in <a
href="https://redirect.github.com/actions/setup-python/pull/1333">actions/setup-python#1333</a></li>
<li>Upgrade <code>@​actions/cache</code> to 6.2.0 by <a
href="https://github.com/philip-gai"><code>@​philip-gai</code></a> in <a
href="https://redirect.github.com/actions/setup-python/pull/1337">actions/setup-python#1337</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/lmvysakh"><code>@​lmvysakh</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-python/pull/1335">actions/setup-python#1335</a></li>
<li><a
href="https://github.com/philip-gai"><code>@​philip-gai</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/setup-python/pull/1337">actions/setup-python#1337</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-python/compare/v6...v7.0.0">https://github.com/actions/setup-python/compare/v6...v7.0.0</a></p>
<h2>v6.3.0</h2>
<h2>What's Changed</h2>
<h3>Enhancement</h3>
<ul>
<li>Add RHEL support and include Linux distro in cache keys by <a
href="https://github.com/priyagupta108"><code>@​priyagupta108</code></a>
in <a
href="https://redirect.github.com/actions/setup-python/pull/1323">actions/setup-python#1323</a></li>
<li>Fix pip cache error handling on Windows by <a
href="https://github.com/priyagupta108"><code>@​priyagupta108</code></a>
in <a
href="https://redirect.github.com/actions/setup-python/pull/1040">actions/setup-python#1040</a></li>
</ul>
<h3>Dependency update</h3>
<ul>
<li>Upgrade minimatch from 3.1.2 to 3.1.5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/setup-python/pull/1281">actions/setup-python#1281</a></li>
<li>Upgrade actions dependencies by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a>
with <a href="https://github.com/Copilot"><code>@​Copilot</code></a> in
<a
href="https://redirect.github.com/actions/setup-python/pull/1303">actions/setup-python#1303</a></li>
<li>Upgrade <code>@​actions/cache</code> to 5.1.0, log cache write
denied by <a
href="https://github.com/jasongin"><code>@​jasongin</code></a> in <a
href="https://redirect.github.com/actions/setup-python/pull/1324">actions/setup-python#1324</a></li>
<li>Upgrade dependency versions and test workflow configuration by <a
href="https://github.com/HarithaVattikuti"><code>@​HarithaVattikuti</code></a>
in <a
href="https://redirect.github.com/actions/setup-python/pull/1322">actions/setup-python#1322</a></li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Update advanced-usage.md by <a
href="https://github.com/Dunky-Z"><code>@​Dunky-Z</code></a> in <a
href="https://redirect.github.com/actions/setup-python/pull/811">actions/setup-python#811</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a>
with <a href="https://github.com/Copilot"><code>@​Copilot</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-python/pull/1303">actions/setup-python#1303</a></li>
<li><a href="https://github.com/jasongin"><code>@​jasongin</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-python/pull/1324">actions/setup-python#1324</a></li>
<li><a href="https://github.com/Dunky-Z"><code>@​Dunky-Z</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/setup-python/pull/811">actions/setup-python#811</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-python/compare/v6.2.0...v6.3.0">https://github.com/actions/setup-python/compare/v6.2.0...v6.3.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/setup-python/commit/5fda3b95a4ea91299a34e894583c3862153e4b97"><code>5fda3b9</code></a>
Pin SHA commits and update docs with latest versions (<a
href="https://redirect.github.com/actions/setup-python/issues/1338">#1338</a>)</li>
<li><a
href="https://github.com/actions/setup-python/commit/4ab7e95f05e168b4356aebde89dd84f59c283d8e"><code>4ab7e95</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/setup-python/issues/1337">#1337</a>
from actions/philip-gai/bump-actions-cache-6-2-0</li>
<li><a
href="https://github.com/actions/setup-python/commit/0f3a009f475dbea83c0371cd85d099690fee8c5c"><code>0f3a009</code></a>
Remove the pip-install input (<a
href="https://redirect.github.com/actions/setup-python/issues/1336">#1336</a>)</li>
<li><a
href="https://github.com/actions/setup-python/commit/f8cf4291c8b8e273ddd26e569454615c7315d932"><code>f8cf429</code></a>
Migrate to ESM and upgrade dependencies (<a
href="https://redirect.github.com/actions/setup-python/issues/1330">#1330</a>)</li>
<li><a
href="https://github.com/actions/setup-python/commit/54baeea5b34417d10a7479663a23cca53ea209b5"><code>54baeea</code></a>
Validate and retry manifest fetch to prevent silent failures (<a
href="https://redirect.github.com/actions/setup-python/issues/1332">#1332</a>)</li>
<li><a
href="https://github.com/actions/setup-python/commit/c7092773a316760f4ecfe498e4af668a4dafeac5"><code>c709277</code></a>
Annotation code fix (<a
href="https://redirect.github.com/actions/setup-python/issues/1335">#1335</a>)</li>
<li><a
href="https://github.com/actions/setup-python/commit/6849080452e69b330395e8a6d23cf90f56d76a1a"><code>6849080</code></a>
remove EOL Python versions and Bumps numpy text fixture (<a
href="https://redirect.github.com/actions/setup-python/issues/1333">#1333</a>)</li>
<li><a
href="https://github.com/actions/setup-python/commit/0903b469fbf4441aadfe4f4b249dc5b1fba3a73e"><code>0903b46</code></a>
Bump certifi from 2020.6.20 to 2024.7.4 in /<strong>tests</strong>/data
(<a
href="https://redirect.github.com/actions/setup-python/issues/1328">#1328</a>)</li>
<li><a
href="https://github.com/actions/setup-python/commit/ece7cb06caefa5fff74198d8649806c4678c61a1"><code>ece7cb0</code></a>
Fix pip cache error handling on Windows. (<a
href="https://redirect.github.com/actions/setup-python/issues/1040">#1040</a>)</li>
<li><a
href="https://github.com/actions/setup-python/commit/1d18d7af5f767c1259ede05a0a5bcc30f3dcf1cf"><code>1d18d7a</code></a>
Update advanced-usage.md (<a
href="https://redirect.github.com/actions/setup-python/issues/811">#811</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/setup-python/compare/v6.2.0...5fda3b95a4ea91299a34e894583c3862153e4b97">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-07 08:53:48 +00:00
dependabot[bot]andLudy f268de8e60 build(deps): bump tauri-apps/tauri-action from 0.6.2 to 1.0.0 (#7236)
Bumps
[tauri-apps/tauri-action](https://github.com/tauri-apps/tauri-action)
from 0.6.2 to 1.0.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tauri-apps/tauri-action/releases">tauri-apps/tauri-action's
releases</a>.</em></p>
<blockquote>
<h2>action v1.0.0</h2>
<h2>[1.0.0]</h2>
<ul>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/db4399ef7ed597de7354e35eaa454c2e5e753d42"><code>db4399e</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1194">#1194</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
<strong>Breaking Change</strong>: <code>.app.tar.gz</code> &amp;
<code>.app.tar.gz.sig</code> files will now include the app version like
all other bundles/installers.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/53c88630c5e183c910a2810db86170b7bb4872f6"><code>53c8863</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1315">#1315</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
The download urls in <code>latest.json</code> will now use the github
url instead of the browser download url.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/9b645673d58df9c322c1100484b7c535e6f26cc0"><code>9b64567</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1167">#1167</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
<strong>Breaking Change</strong>: Drop support for Tauri v1 and unstable
v2 (alpha, beta, rc) versions.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/9491f9dffcc417005d28578d4abf403196f3b56c"><code>9491f9d</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1177">#1177</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
<strong>Breaking Change</strong>: The action will now fail if
<code>draft: true</code> is set but the relevant release is not a
draft.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/ab628636a1a8bf55f0280cfbc114ca0acf9485cc"><code>ab62863</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1176">#1176</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
The frontend lockfile detection will now move up the file tree to fix
issues in workspaces.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/adb8bd38d1eaf0fc5a77d959fea93813093ceab6"><code>adb8bd3</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1170">#1170</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
<strong>Breaking Change</strong>: Remove <code>includeRelease</code> and
<code>includeDebug</code>. You can switch to debug builds via
<code>args: --debug</code>. To upload release <em>and</em> debug builds,
run <code>tauri-action</code> twice, preferably in a job matrix for
concurrent builds.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/eea189090a42695c0b1c3c047210d506b1cc5dec"><code>eea1890</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1175">#1175</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
Improved runner detection to prevent silent fails if runner (npm, pnpm,
yarn, bun) was not installed while a lockfile was present.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/acc588b4e23a3fbc5bfc5d77ba29df93f3e43c64"><code>acc588b</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1203">#1203</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
Added initial Android &amp; iOS support. This only includes building
(=== running <code>tauri android|ios build</code>), installing
dependencies and uploading to stores must be done manually.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/2e37e06369f68e7c240ec8d4674ae3fb78c0075d"><code>2e37e06</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1174">#1174</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
Use <code>npm exec</code> instead of <code>npm run</code> if
<code>@tauri-apps/cli</code> was detected with no <code>tauri</code>
script in <code>package.json</code>.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/1898cf45eec50b449c94b796371c727546611573"><code>1898cf4</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1186">#1186</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
Fiixed an issue that caused outdated signatures for macos universal
builds in latest.json when re-running the action on the same release
multiple times.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/5c7403d73ff9d9aab2efc440adbacfe51071418a"><code>5c7403d</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1326">#1326</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
Expose <code>mainBinaryName</code> in
<code>releaseAssetNamePattern</code> and
<code>workflowArtifactNamePattern</code>.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/5d35c8e3181ba12e10be79009a6e94ee163580f4"><code>5d35c8e</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1189">#1189</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
The upload release assets will now have the original file as its
<code>label</code> which will show as the filename on the GitHub Release
page and will be used internally to update assets on reruns and to get
the download urls for latest.json</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/c18827ebec5e7e75f9bc306ba2d02239d012c1fb"><code>c18827e</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1183">#1183</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
<strong>Breaking Change</strong>: Renamed <code>assetNamePattern</code>
to <code>releaseAssetNamePattern</code>.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/766720b094a5b1122967225a75ac244e9ebcac42"><code>766720b</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1172">#1172</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
<strong>Breaking Change</strong>: Removed the feature to automatically
initialize a Tauri project.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/25f373077afb9028b9cc3caf509eb67d86d57066"><code>25f3730</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1208">#1208</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
<strong>Breaking Change</strong>: Renamed
<code>includeUpdaterJson</code> to <code>uploadUpdaterJson</code> for
consistency with other similar options.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/3fc1c3e593927da7a12f0ae45028973d12202ef4"><code>53c8863</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1314">#1315</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
Removed Gitea support.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/6d11c279be59c2d237278e856acccb1ece44d20a"><code>6d11c27</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1185">#1185</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
<strong>Breaking Change</strong>: Removed
<code>updaterJsonKeepUniversal</code>. This is now always enabled.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/7485c956ab7bbf2708a943bf07b3fefc1e230256"><code>7485c95</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1169">#1169</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
Switch from unmaintained <code>@iarna/toml</code> to
<code>smol-toml</code>. No user-facing changes.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/8236c82510173ef930d644f38790c4cf3fd43cf2"><code>8236c82</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1277">#1277</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
<strong>Semi-Breaking Change</strong>: The action will now update the
name and body of existing releases.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/05c1b09fe50e358ee27fa6ae2a8072b3f6e22155"><code>05c1b09</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1178">#1178</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
Added built-in variant of <a
href="https://github.com/actions/upload-artifact/"><code>actions/upload-artifact</code></a>
to easily upload each bundle as a seperate archive. This may be removed
once <a
href="https://redirect.github.com/actions/upload-artifact/issues/331">actions/upload-artifact#331</a>
lands.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/6434f9aaff0a2daf202596b552c34c463d0e43ed"><code>6434f9a</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1296">#1296</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../Ludea"><code>@​Ludea</code></a>)
Added initial support for <code>vite+</code>.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/tauri-apps/tauri-action/blob/dev/CHANGELOG.md">tauri-apps/tauri-action's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>[1.0.0]</h2>
<ul>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/db4399ef7ed597de7354e35eaa454c2e5e753d42"><code>db4399e</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1194">#1194</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
<strong>Breaking Change</strong>: <code>.app.tar.gz</code> &amp;
<code>.app.tar.gz.sig</code> files will now include the app version like
all other bundles/installers.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/53c88630c5e183c910a2810db86170b7bb4872f6"><code>53c8863</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1315">#1315</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
The download urls in <code>latest.json</code> will now use the github
url instead of the browser download url.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/9b645673d58df9c322c1100484b7c535e6f26cc0"><code>9b64567</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1167">#1167</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
<strong>Breaking Change</strong>: Drop support for Tauri v1 and unstable
v2 (alpha, beta, rc) versions.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/9491f9dffcc417005d28578d4abf403196f3b56c"><code>9491f9d</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1177">#1177</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
<strong>Breaking Change</strong>: The action will now fail if
<code>draft: true</code> is set but the relevant release is not a
draft.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/ab628636a1a8bf55f0280cfbc114ca0acf9485cc"><code>ab62863</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1176">#1176</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
The frontend lockfile detection will now move up the file tree to fix
issues in workspaces.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/adb8bd38d1eaf0fc5a77d959fea93813093ceab6"><code>adb8bd3</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1170">#1170</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
<strong>Breaking Change</strong>: Remove <code>includeRelease</code> and
<code>includeDebug</code>. You can switch to debug builds via
<code>args: --debug</code>. To upload release <em>and</em> debug builds,
run <code>tauri-action</code> twice, preferably in a job matrix for
concurrent builds.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/eea189090a42695c0b1c3c047210d506b1cc5dec"><code>eea1890</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1175">#1175</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
Improved runner detection to prevent silent fails if runner (npm, pnpm,
yarn, bun) was not installed while a lockfile was present.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/acc588b4e23a3fbc5bfc5d77ba29df93f3e43c64"><code>acc588b</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1203">#1203</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
Added initial Android &amp; iOS support. This only includes building
(=== running <code>tauri android|ios build</code>), installing
dependencies and uploading to stores must be done manually.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/2e37e06369f68e7c240ec8d4674ae3fb78c0075d"><code>2e37e06</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1174">#1174</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
Use <code>npm exec</code> instead of <code>npm run</code> if
<code>@tauri-apps/cli</code> was detected with no <code>tauri</code>
script in <code>package.json</code>.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/1898cf45eec50b449c94b796371c727546611573"><code>1898cf4</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1186">#1186</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
Fiixed an issue that caused outdated signatures for macos universal
builds in latest.json when re-running the action on the same release
multiple times.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/5c7403d73ff9d9aab2efc440adbacfe51071418a"><code>5c7403d</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1326">#1326</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
Expose <code>mainBinaryName</code> in
<code>releaseAssetNamePattern</code> and
<code>workflowArtifactNamePattern</code>.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/5d35c8e3181ba12e10be79009a6e94ee163580f4"><code>5d35c8e</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1189">#1189</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
The upload release assets will now have the original file as its
<code>label</code> which will show as the filename on the GitHub Release
page and will be used internally to update assets on reruns and to get
the download urls for latest.json</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/c18827ebec5e7e75f9bc306ba2d02239d012c1fb"><code>c18827e</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1183">#1183</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
<strong>Breaking Change</strong>: Renamed <code>assetNamePattern</code>
to <code>releaseAssetNamePattern</code>.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/766720b094a5b1122967225a75ac244e9ebcac42"><code>766720b</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1172">#1172</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
<strong>Breaking Change</strong>: Removed the feature to automatically
initialize a Tauri project.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/25f373077afb9028b9cc3caf509eb67d86d57066"><code>25f3730</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1208">#1208</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
<strong>Breaking Change</strong>: Renamed
<code>includeUpdaterJson</code> to <code>uploadUpdaterJson</code> for
consistency with other similar options.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/3fc1c3e593927da7a12f0ae45028973d12202ef4"><code>53c8863</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1314">#1315</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
Removed Gitea support.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/6d11c279be59c2d237278e856acccb1ece44d20a"><code>6d11c27</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1185">#1185</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
<strong>Breaking Change</strong>: Removed
<code>updaterJsonKeepUniversal</code>. This is now always enabled.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/7485c956ab7bbf2708a943bf07b3fefc1e230256"><code>7485c95</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1169">#1169</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
Switch from unmaintained <code>@iarna/toml</code> to
<code>smol-toml</code>. No user-facing changes.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/8236c82510173ef930d644f38790c4cf3fd43cf2"><code>8236c82</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1277">#1277</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
<strong>Semi-Breaking Change</strong>: The action will now update the
name and body of existing releases.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/05c1b09fe50e358ee27fa6ae2a8072b3f6e22155"><code>05c1b09</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1178">#1178</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
Added built-in variant of <a
href="https://github.com/actions/upload-artifact/"><code>actions/upload-artifact</code></a>
to easily upload each bundle as a seperate archive. This may be removed
once <a
href="https://redirect.github.com/actions/upload-artifact/issues/331">actions/upload-artifact#331</a>
lands.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/6434f9aaff0a2daf202596b552c34c463d0e43ed"><code>6434f9a</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1296">#1296</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../Ludea"><code>@​Ludea</code></a>)
Added initial support for <code>vite+</code>.</li>
</ul>
<h2>[0.6.2]</h2>
<ul>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/73e111f47ab8fda0af8f09997c91a9e1e83182a2"><code>73e111f</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1288">#1288</a>)
The action can now detect the workspace root correctly if the tauri
project is configured as the cargo workspace root.</li>
</ul>
<h2>[0.6.1]</h2>
<ul>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/ad5c2710e257ef83afcf261722eeaa6a33b4e0f7"><code>ad5c271</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1241">#1241</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
Fixed an issue that caused the action to only read one
<code>--config</code> arg.</li>
</ul>
<h2>[0.6.0]</h2>
<ul>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/e918a068fe65bde93e97fe0d4024e45dc568c536"><code>e918a06</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1133">#1133</a>)
Encode <code>tagName</code> option value in <code>latest.json</code>
URL.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/45acc1922a6fe824bcffed136e9b5d80660ed050"><code>45acc19</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1164">#1164</a>)
Add <code>generateReleaseNotes</code> config to use GitHub's release
notes API to auto generate the release name and/or body.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/59a1a70223956f123844e5f309cf5cf82d279685"><code>59a1a70</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1166">#1166</a>)
Fix default file name pattern to match tauri's file names again.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/08112f729df63ddc8f64de706284e8518f1b1e73"><code>08112f7</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1106">#1106</a>)
Added experimental support for Gitea hosted instances.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/08112f729df63ddc8f64de706284e8518f1b1e73"><code>08112f7</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1106">#1106</a>)
Added a config to set the GitHub API URL which should help users with
self-hosted instances and those using GitHub Enterprise.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/b0671399f144ae28a851ef8c3e0540b5d5c6cd37"><code>b067139</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1130">#1130</a>)
The action now uses node v24 which raises the minimum GitHub runner
version to <code>v2.327.1</code>.</li>
</ul>
<h2>[0.5.24]</h2>
<ul>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/c5d6ac7494763a167f5acf6e6c73664fc7360468"><code>c5d6ac7</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1152">#1152</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../FabianLars"><code>@​FabianLars</code></a>)
The <code>latest.json</code> file now contains
<code>{os}-{arch}-{installer}</code> keys as well to support multiple
installer formats per platform. This requires
<code>tauri-plugin-updater</code> version <code>2.10.0</code> or
above.</li>
<li><a
href="https://www.github.com/tauri-apps/tauri-action/commit/0085932c0f8ac87d9757582e84c0fc6fbc0f7699"><code>0085932</code></a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/pull/1141">#1141</a>
by <a
href="https://www.github.com/tauri-apps/tauri-action/../../jarjk"><code>@​jarjk</code></a>)
Added option to upload the app's binary alongside installers.</li>
</ul>
<h2>[0.5.23]</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/tauri-apps/tauri-action/commit/1deb371b0cd8bd54025b384f1cd735e725c4060f"><code>1deb371</code></a>
Apply Version Updates From Current Changes (<a
href="https://redirect.github.com/tauri-apps/tauri-action/issues/1191">#1191</a>)</li>
<li><a
href="https://github.com/tauri-apps/tauri-action/commit/35033cf3e55baf3cfc8bb7f3fdaeb41c908aa00d"><code>35033cf</code></a>
chore(deps): update dependency <code>@​vercel/ncc</code> to v0.44.0 (<a
href="https://redirect.github.com/tauri-apps/tauri-action/issues/1323">#1323</a>)</li>
<li><a
href="https://github.com/tauri-apps/tauri-action/commit/5ead4aa94ca5172c8b45b46849a512396ad5b7e4"><code>5ead4aa</code></a>
refactor: simplify createArtifact function (<a
href="https://redirect.github.com/tauri-apps/tauri-action/issues/1327">#1327</a>)</li>
<li><a
href="https://github.com/tauri-apps/tauri-action/commit/789968274b5c40e92c1081032f8d3da9be086756"><code>7899682</code></a>
ci: update actions (<a
href="https://redirect.github.com/tauri-apps/tauri-action/issues/1328">#1328</a>)</li>
<li><a
href="https://github.com/tauri-apps/tauri-action/commit/f201b79671e09564969e834a03be5dde2f6c45b8"><code>f201b79</code></a>
chore(deps): update dependency <code>@​types/node</code> to v24.13.2 (<a
href="https://redirect.github.com/tauri-apps/tauri-action/issues/1320">#1320</a>)</li>
<li><a
href="https://github.com/tauri-apps/tauri-action/commit/5c7403d73ff9d9aab2efc440adbacfe51071418a"><code>5c7403d</code></a>
feat: default naming scheme for mobile assets, closes <a
href="https://redirect.github.com/tauri-apps/tauri-action/issues/1317">#1317</a>
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/issues/1326">#1326</a>)</li>
<li><a
href="https://github.com/tauri-apps/tauri-action/commit/71e781de46da4143f68c4058e087bc2278b6db8b"><code>71e781d</code></a>
chore(deps): update dependency <code>@​biomejs/biome</code> to v2.5.1
(<a
href="https://redirect.github.com/tauri-apps/tauri-action/issues/1318">#1318</a>)</li>
<li><a
href="https://github.com/tauri-apps/tauri-action/commit/f51677a4652aed7164481c63c47293a5f0da9af1"><code>f51677a</code></a>
chore(deps): update dependency smol-toml to v1.7.0 (<a
href="https://redirect.github.com/tauri-apps/tauri-action/issues/1324">#1324</a>)</li>
<li><a
href="https://github.com/tauri-apps/tauri-action/commit/61337b436e85a75347fec553393c9e2be16a0d85"><code>61337b4</code></a>
chore(deps): update dependency <code>@​types/node</code> to v24.12.3 (<a
href="https://redirect.github.com/tauri-apps/tauri-action/issues/1316">#1316</a>)</li>
<li><a
href="https://github.com/tauri-apps/tauri-action/commit/53c88630c5e183c910a2810db86170b7bb4872f6"><code>53c8863</code></a>
refactor: Use github api as download url in latest.json (<a
href="https://redirect.github.com/tauri-apps/tauri-action/issues/1315">#1315</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/tauri-apps/tauri-action/compare/84b9d35b5fc46c1e45415bdb6144030364f7ebc5...1deb371b0cd8bd54025b384f1cd735e725c4060f">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-07 08:53:35 +00:00
ConnorYohandReece Browne 75be292176 feat(editor): move app config onto TanStack Query (#7283)
# Description of Changes

> Stacked on #7264 — review that first. This diff is against its branch.

## The problem

`AppConfigContext` hand-rolled a query client: a `fetchCountRef` dedupe
guard, an exponential-backoff retry loop with its own `sleep()`, a
`hasResolvedConfig` flag, and manual 401/5xx branching. All to fetch one
endpoint that 80 files read.

## End state

Same provider, same public contract, React Query underneath. **250 lines
to 142**, and no consumer file changes.

| | Before | After |
|---|---|---|
| Dedupe | `fetchCountRef` guard | query key |
| Retry | `for` loop + `sleep()` + backoff maths | `retry` +
`retryDelay` |
| 401 | caught in the component, sets default config | `fetchAppConfig`
returns the default — the retry predicate and error state only see real
failures |
| Auth pages | early return inside the fetch | `enabled` |
| Resolved-yet tracking | `hasResolvedConfig` state | derived from the
query |

`fetchAppConfig` moves to `core/api/config.ts` with the simulation hook
and request options, so the context no longer knows how config is
fetched.

**Behaviour change:** config survives a provider remount instead of
refetching. That matters on desktop, where a connection-mode switch
remounts the tree — and #7264's cache reset already clears it on exactly
that transition.

## Testing

The existing 12-case contract test passes unchanged apart from the query
wrapper. It caught a real mistake: `failureCount` is 0-based in v5, so
`<= maxRetries` gave one attempt too many.

Four cases added — cached remount, `maxRetries` honoured, 4xx not
retried, `autoFetch` off.

`task frontend:check` green: 1672 tests across 191 files, typecheck on
all five flavours, eslint, dpdm, prettier.

## Coming next

| PR | Scope |
|---|---|
| 3 | `useEndpointConfig` — core (251 lines) plus a 482-line desktop
override with its own dependency polling. Split out of this PR;
different risk profile, and it deserves its own review. |
| 4 | `useAdminSettings` (20 consumers) and the config sections |
| 5 | Polling loops → `refetchInterval` |
| 6 | Finish the Processor, collapse to one client |
| 7 | Tool execution — mutation state only |

---------

Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
2026-08-07 08:53:13 +00:00
brios fd1c955648 refactor(ui): redesign VersionTimeline UI (#7162)
# Description of Changes

I felt the old VersionTimeline was a bit too crowded/not very "good"
looking so i had a crack at redesigning it. Mainly aimed for:

- less info
- less crowding
- more spacing

### New


<img width="884" height="1172" alt="image"
src="https://github.com/user-attachments/assets/070eab35-80b8-408d-9917-b111c433ae4f"
/>


### Old

<img width="882" height="770" alt="image"
src="https://github.com/user-attachments/assets/a074ba2e-79e3-4a8d-9491-ad3a8c432b98"
/>


<!--
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-07 08:52:13 +00:00
Anthony Stirling 2cf6db99ce Fix timing-fragile Valkey rate-limit boundary test (#7302)
# Description of Changes

Fix timing-fragile Valkey rate-limit boundary test

---

## 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-07 08:47:09 +00:00
Ludy ef63ba83a2 Merge branch 'main' into update_python_dep_20260804 2026-08-06 15:32:12 +02:00
Ludy87 a59196db69 rm npm 2026-08-04 18:53:33 +02:00
Ludy87 bfa86b099d Install npm in embedded Docker images
Add npm to the apt-get install line in docker/embedded/Dockerfile, Dockerfile.fat and Dockerfile.ultra-lite so npm is available in embedded builds. Also add an npm --version check in the ultra-lite Dockerfile. This ensures frontend/build tooling is present in the embedded images.
2026-08-04 18:30:27 +02:00
Ludy87 4bfc3fbb4b Remove BLE001 noqa comments and adjust type hints
Drop redundant "# noqa: BLE001" inline comments from broad except blocks and update type annotations for async/generator signatures. Changes: remove BLE001 noqa in config.py and service.py, tighten ConcurrencyLimitedModel.request_stream return type in runtime.py to AsyncGenerator[StreamedResponse], simplify test generator annotation in test_config_routes.py, and a minor README formatting tweak. These edits align code with linting/typing rules and improve type correctness.
2026-08-04 18:10:33 +02:00
Ludy87 8be041c4be Use AsyncGenerator/Generator typing
Replace collections.abc AsyncIterator/Iterator with AsyncGenerator/Generator in engine runtime and tests. Update ConcurrencyLimitedModel.request_stream return annotation to AsyncGenerator[StreamedResponse, None] and adjust test_config_routes._client to Generator[TestClient, None, None]. Typing-only change to reflect actual generator/async-generator return shapes; no runtime behaviour changes.
2026-08-04 18:00:59 +02:00
Ludy87 2c11772b7f deps(engine): update Python dependencies 2026-08-04 17:50:58 +02:00
3595 changed files with 113748 additions and 52229 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ vs `main`. Flags: `--html` (also emit a rendered HTML twin), `--no-screens`.
- Read the PR description / commit messages for stated intent. Do **not** invent
history or motivation that isn't evidenced (state current behavior in present tense).
- Classify touched files by layer:
- **Frontend**: tools (`frontend/src/editor/core/components/tools/*` or `.../core/tools/*`),
- **Frontend**: tools (`frontend/editor/src/core/components/tools/*` or `.../core/tools/*`),
hooks (`core/hooks/tools/*`, `useToolOperation`), contexts, routes, i18n
(`public/locales/en-US`).
- **Java backend**: controllers (`.../controller/api/...`), services, models, config.
+5 -5
View File
@@ -53,11 +53,11 @@ gh pr diff <pr> --name-only # or: git diff --name-only <base>...HEAD
Map changed frontend files to URLs generically:
- **Tools**: a changed `components/tools/<toolDir>/…` or `hooks/tools/<tool>/…`
toolId → URL via the repo's own rule `getToolUrlPath` in
[toolsTaxonomy.ts:200](frontend/src/editor/core/data/toolsTaxonomy.ts): `/` + the
[toolsTaxonomy.ts:200](frontend/editor/src/core/data/toolsTaxonomy.ts): `/` + the
id kebab-cased (`addPageNumbers``/add-page-numbers`).
- **Pages/routes**: changed `filesPage/*``/files`, etc.
- `--all`: enumerate every tool in the registry instead of just changed ones.
Write `frontend/screenshots/ui-diff/targets.json` =
Write `frontend/editor/screenshots/ui-diff/targets.json` =
`[{ "id":"compress", "url":"/compress", "name":"Compress" }]`. This is what makes
it generic - the spec never names a tool.
@@ -69,12 +69,12 @@ the full viewport - or the `--scope` container if given). Ensure the harness is
(node_modules + icons).
```
# after = current head
cd frontend && PR_SHOT_SIDE=after PR_SHOT_THEME=light \
cd frontend/editor && PR_SHOT_SIDE=after PR_SHOT_THEME=light \
npx playwright test --project=stubbed ui-before-after.spec.ts
# before = base, in an isolated worktree (copy the spec + targets.json in)
git worktree add ../ba-base origin/<baseRefName> # or the merge-base
# set up its frontend, copy spec + screenshots/ui-diff/targets.json across, then:
cd ../ba-base/frontend && PR_SHOT_SIDE=before PR_SHOT_THEME=light \
cd ../ba-base/frontend/editor && PR_SHOT_SIDE=before PR_SHOT_THEME=light \
npx playwright test --project=stubbed ui-before-after.spec.ts
# copy its screenshots/ui-diff/before/ back next to after/. Repeat with
# PR_SHOT_THEME=dark if --theme includes dark. Remove worktree when done.
@@ -82,7 +82,7 @@ cd ../ba-base/frontend && PR_SHOT_SIDE=before PR_SHOT_THEME=light \
### 4. Auto-diff (surface what changed)
```
cd frontend && node <skill>/diff-shots.mjs \
cd frontend/editor && node <skill>/diff-shots.mjs \
screenshots/ui-diff/before screenshots/ui-diff/after screenshots/ui-diff
```
Produces `diff-report.json` classifying each view `unchanged | changed | added |
@@ -2,7 +2,7 @@
// unchanged | changed | added | removed, and CROP each changed pair to the
// affected region (bounding box of differing pixels + padding) - unless the
// change spans most of the page, in which case the full frame is kept.
// Run from frontend (so deps resolve):
// Run from frontend/editor (so deps resolve):
// node <skill>/diff-shots.mjs <beforeDir> <afterDir> [outDir]
// Env:
// DIFF_THRESHOLD min fraction of differing pixels to count as changed (default 0.001)
@@ -1,5 +1,5 @@
// Render each .tab-section of a montage HTML into its own PNG (the PR-ready image).
// Run from frontend (so @playwright/test resolves):
// Run from frontend/editor (so @playwright/test resolves):
// node <skill>/shoot-sections.mjs <montage.html> <outDir>
import path from "node:path";
import { pathToFileURL } from "node:url";
+6 -6
View File
@@ -28,13 +28,13 @@ current branch diff. Flags: `--fix`, `--theme light|dark|both` (default both),
## What this repo gives you (use it, don't reinvent)
- **Stubbed Playwright project** = backend-free screenshots via `page.route()` mocks.
Reference implementation: `frontend/src/editor/core/tests/stubbed/files-page-screenshots.spec.ts`.
Reference implementation: `frontend/editor/src/core/tests/stubbed/files-page-screenshots.spec.ts`.
It already shows the light / **dark** / **RTL** passes, JWT seeding, IndexedDB
seeding, and dumping PNGs to a `screenshots/<area>/` folder. Copy its shape.
- Helpers: `frontend/src/editor/core/tests/helpers/ui-helpers.ts`
- Helpers: `frontend/editor/src/core/tests/helpers/ui-helpers.ts`
(`uploadFiles`, `openSettings`, `waitForModalOpen`, `dismissTourTooltip`, …)
and the `stub-test-base` fixtures (`autoGoto`, `seedJwt`, `viewport`).
- Config: `frontend/playwright.config.ts` (run from `frontend/`).
- Config: `frontend/editor/playwright.config.ts` (run from `frontend/editor/`).
- Report template: [report-template.html](report-template.html) - self-contained,
one big image at a time, a global light/dark slider that flips every shot,
thumbnail rail, prev/next + arrow keys, and a Findings tab.
@@ -54,13 +54,13 @@ current branch diff. Flags: `--fix`, `--theme light|dark|both` (default both),
Worktrees have no `node_modules` and no generated icons. From repo root:
```
cd frontend && npm ci # or junction main's node_modules (see memory)
cd frontend && node scripts/generate-icons.js
cd frontend/editor && node scripts/generate-icons.js
```
Kill any stale dev server first (it serves old modules):
`Get-NetTCPConnection -LocalPort 5173 -State Listen | %{ Stop-Process -Id $_.OwningProcess -Force }`
### 3. Write the capture spec
Create `frontend/src/editor/core/tests/stubbed/<feature>-walkthrough.spec.ts`,
Create `frontend/editor/src/core/tests/stubbed/<feature>-walkthrough.spec.ts`,
modeled on `files-page-screenshots.spec.ts`. For each enumerated view:
- stub the APIs it needs, drive the UI to that state, wait on a real locator
(not a fixed sleep), `await settle(page)` for Mantine portals, then
@@ -71,7 +71,7 @@ modeled on `files-page-screenshots.spec.ts`. For each enumerated view:
- Name shots `NN_<view>_<theme>.png` so light/dark pair up by suffix.
- Prefer **stable test-ids** over translated accessible names (RTL/i18n breaks text locators).
Run it: `cd frontend && npx playwright test --project=stubbed <feature>-walkthrough.spec.ts`.
Run it: `cd frontend/editor && npx playwright test --project=stubbed <feature>-walkthrough.spec.ts`.
Add `--project=stubbed-firefox`/`-webkit` only if cross-browser layout matters.
### 4. Build the report
-2
View File
@@ -119,8 +119,6 @@
"extensions": [
"elagil.pre-commit-helper", // Support for pre-commit hooks to enforce code quality
"josevseb.google-java-format-for-vs-code", // Google Java code formatter to follow the Google Java Style Guide
"ms-python.black-formatter", // Python code formatter using Black
"ms-python.flake8", // Flake8 linter for Python to enforce code quality
"ms-python.python", // Official Microsoft Python extension with IntelliSense, debugging, and Jupyter support
"ms-vscode-remote.vscode-remote-extensionpack", // Remote Development Pack for SSH, WSL, and Containers
// "Oracle.oracle-java", // Oracle Java extension with additional features for Java development
+4 -4
View File
@@ -26,16 +26,16 @@ version_builds/
node_modules/
**/node_modules/
frontend/node_modules/
frontend/dist/
frontend/playwright-report/
frontend/editor/dist/
frontend/editor/playwright-report/
.npm/
.yarn/
# Tauri/desktop builds
src-tauri/target/
src-tauri/dist/
frontend/src-tauri/target/
frontend/src-tauri/dist/
frontend/editor/src-tauri/target/
frontend/editor/src-tauri/dist/
# IDE and editor
.idea/
+23 -13
View File
@@ -1,18 +1,28 @@
# All PRs must be approved by Frooodle or Ludy87
* @Frooodle @Ludy87 @jbrunton96 @ConnorYoh
# Review ownership is assigned to teams where possible.
# Teams can only contain org members, so outside collaborators are listed by hand.
#
# @Stirling-Tools/maintainers - Frooodle, jbrunton96, ConnorYoh
# @Stirling-Tools/backend-reviewers - Frooodle, jbrunton96, ConnorYoh
# @Stirling-Tools/frontend-reviewers - Frooodle, jbrunton96, ConnorYoh, reecebrowne, EthanHealy01
# @Stirling-Tools/devops-reviewers - Frooodle, jbrunton96, ConnorYoh
# @Stirling-Tools/all - all of the above
#
# Outside collaborators (need Write access to count as owners): @Ludy87 @balazs-szucs
# Default owners for everything
* @Stirling-Tools/maintainers @Ludy87
# Backend
/app/** @DarioGii @Frooodle @Ludy87 @jbrunton96 @ConnorYoh @balazs-szucs
/app/** @Stirling-Tools/backend-reviewers @Ludy87 @balazs-szucs
#V2 frontend
/frontend/** @reecebrowne @ConnorYoh @EthanHealy01 @jbrunton96 @Frooodle @balazs-szucs
/app/core/src/main/resources/static/** @reecebrowne @ConnorYoh @EthanHealy01 @jbrunton96 @Frooodle @Ludy87 @balazs-szucs
# V2 frontend
/frontend/** @Stirling-Tools/frontend-reviewers @balazs-szucs
/app/core/src/main/resources/static/** @Stirling-Tools/frontend-reviewers @Ludy87 @balazs-szucs
#V2 docker
/docker/backend/** @Frooodle @Ludy87 @DarioGii
/docker/frontend/** @reecebrowne @ConnorYoh @EthanHealy01 @jbrunton96 @Frooodle @Ludy87
/docker/compose/** @reecebrowne @ConnorYoh @EthanHealy01 @DarioGii @jbrunton96 @Frooodle @Ludy87
# V2 docker
/docker/backend/** @Stirling-Tools/devops-reviewers @Ludy87
/docker/frontend/** @Stirling-Tools/frontend-reviewers @Stirling-Tools/devops-reviewers @Ludy87
/docker/compose/** @Stirling-Tools/frontend-reviewers @Stirling-Tools/devops-reviewers @Ludy87
#GHA (All users)
/.github/** @reecebrowne @ConnorYoh @EthanHealy01 @DarioGii @jbrunton96 @Frooodle @Ludy87 @balazs-szucs
# GHA (all users)
/.github/** @Stirling-Tools/all @Ludy87 @balazs-szucs
+10 -10
View File
@@ -92,12 +92,12 @@ frontend: &frontend
# job on changes to any of these.
tauri: &tauri
- *ci
- frontend/src-tauri/**
- frontend/src/editor/desktop/**
- frontend/tsconfig.desktop.vite.json
- frontend/editor/src-tauri/**
- frontend/editor/src/desktop/**
- frontend/editor/tsconfig.desktop.vite.json
- frontend/package.json
- frontend/package-lock.json
- frontend/vite.config.ts
- frontend/editor/vite.config.ts
- .github/workflows/tauri-build.yml
- Taskfile.yml
- .taskfiles/desktop.yml
@@ -121,9 +121,9 @@ engine: &engine
generated-models: &generated-models
- *ci
- *openapi
- frontend/scripts/generate-tool-api-types.mts
- frontend/src/editor/core/types/toolApiTypes.ts
- frontend/src/editor/core/types/toolIO.ts
- frontend/editor/scripts/generate-tool-api-types.mts
- frontend/editor/src/core/types/toolApiTypes.ts
- frontend/editor/src/core/types/toolIO.ts
- engine/scripts/generate_tool_models.py
- engine/src/stirling/models/tool_models.py
- engine/src/stirling/models/tool_io.py
@@ -135,7 +135,7 @@ licenses-frontend: &licenses-frontend
- ".github/workflows/frontend-backend-licenses-update.yml"
- "frontend/package.json"
- "frontend/package-lock.json"
- "frontend/scripts/generate-licenses.js"
- "frontend/editor/scripts/generate-licenses.js"
licenses-backend: &licenses-backend
- ".github/workflows/frontend-backend-licenses-update.yml"
@@ -146,8 +146,8 @@ licenses-backend: &licenses-backend
proprietary: &proprietary
- *ci
- app/proprietary/**
- frontend/src/editor/proprietary/**
- frontend/src/editor/core/tests/enterprise/**
- frontend/editor/src/proprietary/**
- frontend/editor/src/core/tests/enterprise/**
- testing/compose/docker-compose-keycloak-oauth.yml
- testing/compose/docker-compose-keycloak-saml.yml
- testing/compose/keycloak-realm-oauth.json
-1
View File
@@ -11,7 +11,6 @@
"LaserKaspar",
"sbplat",
"reecebrowne",
"DarioGii",
"ConnorYoh",
"EthanHealy01",
"jbrunton96",
+8 -11
View File
@@ -16,6 +16,11 @@ updates:
cooldown:
default-days: 7
rebase-strategy: "auto"
groups:
simple-java-mail:
patterns:
- "org.simplejavamail:simple-java-mail"
- "org.simplejavamail:outlook-module"
- package-ecosystem: "docker"
directories:
@@ -73,14 +78,6 @@ updates:
- "react-dom"
- "@types/react"
- "@types/react-dom"
typescript-eslint:
patterns:
- "@typescript-eslint/*"
- "typescript-eslint"
eslint:
patterns:
- "eslint"
- "@eslint/*"
vite:
patterns:
- "vite"
@@ -128,9 +125,9 @@ updates:
- package-ecosystem: cargo
directories:
- /frontend/src-tauri
- /frontend/src-tauri/thumbnail-handler
- /frontend/src-tauri/provisioner
- /frontend/editor/src-tauri
- /frontend/editor/src-tauri/thumbnail-handler
- /frontend/editor/src-tauri/provisioner
schedule:
interval: "weekly"
cooldown:
+3 -3
View File
@@ -51,7 +51,7 @@ labels:
- label: 'Translation'
files:
- 'frontend/public/locales/[a-zA-Z]{2}-[a-zA-Z\-]{2,7}/translation.toml'
- 'frontend/editor/public/locales/[a-zA-Z]{2}-[a-zA-Z\-]{2,7}/translation.toml'
- 'scripts/ignore_translation.toml'
- 'scripts/remove_translation_keys.sh'
- 'scripts/replace_translation_line.sh'
@@ -70,8 +70,8 @@ labels:
- label: 'Tauri'
files:
- 'frontend/src-tauri/**'
- 'frontend/src-tauri/.*'
- 'frontend/editor/src-tauri/**'
- 'frontend/editor/src-tauri/.*'
- label: 'engine'
files:
+16 -35
View File
@@ -10,18 +10,18 @@ adjusting the format.
Usage:
python check_language_toml.py --reference-file <path_to_reference_file> --branch <branch_name> [--actor <actor_name>] [--files <list_of_changed_files>]
"""
""" # noqa: E501
# Sample for Windows:
# python .github/scripts/check_language_toml.py --reference-file frontend/public/locales/en-US/translation.toml --branch "" --files frontend/public/locales/de-DE/translation.toml frontend/public/locales/fr-FR/translation.toml
# python .github/scripts/check_language_toml.py --reference-file frontend/editor/public/locales/en-US/translation.toml --branch "" --files frontend/editor/public/locales/de-DE/translation.toml frontend/editor/public/locales/fr-FR/translation.toml # noqa: E501
import argparse
import glob
import os
import re
import tomllib # Python 3.11+ (stdlib)
from pathlib import Path
import tomllib # Python 3.11+ (stdlib)
import tomli_w # For writing TOML files
@@ -133,11 +133,7 @@ def update_missing_keys(reference_file, file_list, branch=""):
file_path = Path(file_path)
language_dir = file_path.parent.name
reference_lang_dir = reference_file.parent.name
if (
language_dir == reference_lang_dir
or file_path.suffix != ".toml"
or file_path.parents[1].name != "locales"
):
if language_dir == reference_lang_dir or file_path.suffix != ".toml" or file_path.parents[1].name != "locales":
print(f"Skipping file: {file_path}")
continue
@@ -198,9 +194,7 @@ def check_for_differences(reference_file, file_list, branch, actor):
# Verify that file is within the expected directory
if not absolute_path.is_relative_to(base_dir):
has_differences = True
report.append(
f"\n⚠️ Unsafe file found: `{locale_dir}/{basename_current_file}`\n\n---\n"
)
report.append(f"\n⚠️ Unsafe file found: `{locale_dir}/{basename_current_file}`\n\n---\n")
continue
# Verify file size before processing
@@ -214,10 +208,7 @@ def check_for_differences(reference_file, file_list, branch, actor):
if basename_current_file == basename_reference_file and locale_dir == "en-US":
continue
if (
file_normpath.suffix != ".toml"
or basename_current_file != "translation.toml"
):
if file_normpath.suffix != ".toml" or basename_current_file != "translation.toml":
continue
only_reference_file = False
@@ -232,11 +223,11 @@ def check_for_differences(reference_file, file_list, branch, actor):
has_differences = True
if reference_key_count > current_key_count:
report.append(
f" - **_Mismatched key count_**: {reference_key_count} (reference) vs {current_key_count} (current). Translation keys are missing."
f" - **_Mismatched key count_**: {reference_key_count} (reference) vs {current_key_count} (current). Translation keys are missing." # noqa: E501
)
elif reference_key_count < current_key_count:
report.append(
f" - **_Too many keys_**: {reference_key_count} (reference) vs {current_key_count} (current). Please verify if there are additional keys that need to be removed."
f" - **_Too many keys_**: {reference_key_count} (reference) vs {current_key_count} (current). Please verify if there are additional keys that need to be removed." # noqa: E501
)
else:
report.append("1. **Test Status:** ✅ **_Passed_**")
@@ -257,23 +248,19 @@ def check_for_differences(reference_file, file_list, branch, actor):
report.append(" - **Issue:**")
if missing_keys_list:
report.append(
f" - **_Extra keys in `{locale_dir}/{basename_current_file}`_**: `{missing_keys_str}` that are not present in **_`{basename_reference_file}`_**."
f" - **_Extra keys in `{locale_dir}/{basename_current_file}`_**: `{missing_keys_str}` that are not present in **_`{basename_reference_file}`_**." # noqa: E501
)
report.append("")
report.append(" Use the following command to remove them:")
report.append(
f" `python scripts/translations/translation_merger.py {locale_dir} remove-unused`"
)
report.append(f" `python scripts/translations/translation_merger.py {locale_dir} remove-unused`")
report.append("")
if extra_keys_list:
report.append(
f" - **_Missing keys in `{locale_dir}/{basename_current_file}`_**: `{extra_keys_str}` that are not present in **_`{basename_reference_file}`_**."
f" - **_Missing keys in `{locale_dir}/{basename_current_file}`_**: `{extra_keys_str}` that are not present in **_`{basename_reference_file}`_**." # noqa: E501
)
report.append("")
report.append(" Use the following command to add them:")
report.append(
f" `python scripts/translations/translation_merger.py {locale_dir} add-missing`"
)
report.append(f" `python scripts/translations/translation_merger.py {locale_dir} add-missing`")
report.append("")
if missing_keys_list or extra_keys_list:
@@ -288,9 +275,7 @@ def check_for_differences(reference_file, file_list, branch, actor):
output = "\n".join(
[
f" - `{key}`: first at {first}, duplicate at `{duplicate}`"
for key, first, duplicate in find_duplicate_keys(
branch_path / file_normpath
)
for key, first, duplicate in find_duplicate_keys(branch_path / file_normpath)
]
)
report.append("3. **Test Status:** ❌ **_Failed_**")
@@ -308,23 +293,19 @@ def check_for_differences(reference_file, file_list, branch, actor):
report.append("## ❌ Overall Check Status: **_Failed_**")
report.append("")
report.append(
f"@{actor} please check your translation if it conforms to the standard. Follow the format of [en-US/translation.toml](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/frontend/public/locales/en-US/translation.toml)"
f"@{actor} please check your translation if it conforms to the standard. Follow the format of [en-US/translation.toml](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/frontend/editor/public/locales/en-US/translation.toml)"
)
else:
report.append("## ✅ Overall Check Status: **_Success_**")
report.append("")
report.append(
f"Thanks @{actor} for your help in keeping the translations up to date."
)
report.append(f"Thanks @{actor} for your help in keeping the translations up to date.")
if not only_reference_file:
print("\n".join(report))
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Find missing keys in TOML translation files"
)
parser = argparse.ArgumentParser(description="Find missing keys in TOML translation files")
parser.add_argument(
"--actor",
required=False,
-9
View File
@@ -1,9 +0,0 @@
pip
setuptools
WeasyPrint
pdf2image
pillow
unoserver
opencv-python-headless
pre-commit
brotli @ git+https://github.com/google/brotli.git@028fb5a23661f123017c060daa546b55cf4bde29
-528
View File
@@ -1,528 +0,0 @@
#
# This file is autogenerated by pip-compile with Python 3.13
# by the following command:
#
# pip-compile --allow-unsafe --generate-hashes --output-file='.github\scripts\requirements_dev.txt' --strip-extras '.github\scripts\requirements_dev.in'
#
# WARNING: pip install will require the following package to be hashed.
# Consider using a hashable URL like https://github.com/jazzband/pip-tools/archive/SOMECOMMIT.zip
brotli @ git+https://github.com/google/brotli.git@028fb5a23661f123017c060daa546b55cf4bde29
# via
# -r .github/scripts/requirements_dev.in
# fonttools
cffi==2.1.0 \
--hash=sha256:02cb7ff33ded4f1532476731f89ede53e2e488a8e6205515a82144246ffa7dcc \
--hash=sha256:03e9810d18c646077e501f661b682fbf5dee4676048527ca3cffe66faa9960dd \
--hash=sha256:0520e1f4c35f44e209cbbb421b67eec42e6a157f59444dfb6058874ff3610e5d \
--hash=sha256:0582a58f3051372229ca8e7f5f589f9e5632678208d8636fea3676711fdf7fe5 \
--hash=sha256:0611e7ebf90573a535ebdc33ae9da222d037853983e13359f580fab781ca017f \
--hash=sha256:0a42c688d19fca6e095a53c6a6e2295a5b050a8b289f109adab02a9e61a25de6 \
--hash=sha256:0a96b74cda968eebbad56d973efe5098974f0a9fb323865bf99ea1fd24e3e64c \
--hash=sha256:10537b1df4967ca26d21e5072d7d54188354483b91dc75058968d3f0cf13fbda \
--hash=sha256:11b3fb55f4f8ad92274ed26705f65d8f91457de71f5380061eb6d125a768fecd \
--hash=sha256:15faec4adfff450819f3aee0e2e02c812de6edb88203aa58807955db2003472a \
--hash=sha256:164bff1657b2a74f0b6d54e11c9b375bc97b931f2ca9c43fcf875838da1570dd \
--hash=sha256:1854b724d00f6654c742097d5387569021be12d3a0f770eae1df8f8acfcc6acd \
--hash=sha256:19c54ac121cad98450b4896fa9a43ee0180d57bc4bc911a33db6cab1efab6cd3 \
--hash=sha256:1b96bfe2c4bd825681b7d311ad6d9b7280a091f43e8f63da5729638083cd3bfb \
--hash=sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66 \
--hash=sha256:1ff3456eab0d889592d1936d6125bbfbc7ae4d3354a700f8bd80450a66445d4d \
--hash=sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f \
--hash=sha256:276f20fffd7b396e12516ba8edf9509210ac248cbbc5acbc39cd512f9f59ebe6 \
--hash=sha256:2b71d409cccee78310ab5dec549aed052aaea483346e282c7b02362596e01bb0 \
--hash=sha256:2e9dabb9abcb7ad15938c7196ad5c1718a4e6d33cc79b4c0209bdb64c4a54a5c \
--hash=sha256:30b65779d598c370374fefabf138d456fd6f3216bfa7bedfab1ba82025b0cd93 \
--hash=sha256:33eb1ad83ebe8f313e0df035c406227d55a79456704a863fad9842136af5ad7d \
--hash=sha256:35aaea0c7ee0e58a5cd8c2fd1a48fdf7ece0d2699b7ecdda08194e9ce5dd9b3d \
--hash=sha256:3681e031db29958a7502f5c0c9d6bbc4c36cb20f7b104086fa642d1799631ff8 \
--hash=sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b \
--hash=sha256:37f525a7e7e50c017fdebe58b787be310ad59357ae43a053943a6e1a6c526001 \
--hash=sha256:3b926723c13eba9f81d2ef3820d63aeceec3b2d4639906047bf675cb8a7a500d \
--hash=sha256:3d7f118b5adbfdfead90c25822690b02bc8074fba949bb7858bec4ebd55adb43 \
--hash=sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b \
--hash=sha256:47ff3a8bfd8cb9da1af7524b965127095055654c177fcfc7578debcb015eecd0 \
--hash=sha256:4d433a51f1870e43a13b6732f92aaf540ff77c2015097c78556f75a2d6c030e0 \
--hash=sha256:4f26194e3d95e06501b942642855aed4f953d55e95d7d01b7c4483db3ecff458 \
--hash=sha256:510aeeeac94811b138077451da1fb18b308a5feab47dd2b603af55804155e1c8 \
--hash=sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d \
--hash=sha256:5ecbd0499275d57506d397eebe1981cee87b47fcd9ef5c22cab7ed7644a39a94 \
--hash=sha256:6274dcb2d15cef48daa73ed1be5a40d501d74dccd0cd6db364776d12cb6ba022 \
--hash=sha256:63960549e4f8dc41e31accb97b975abaecfc44c03e396c093a6436763c2ea7db \
--hash=sha256:64c753a0f87a256020004f37a1c8c02c480e725f910f0b2a0f3f07debd1b2479 \
--hash=sha256:6af371f3767faeffc6ac1ef57cdfd25844403e9d3f476c5537caee499de96376 \
--hash=sha256:6ca4919c6e4f89aa99c42510b42cf54596892c00b3f9077f6bdd1505e24b9c8d \
--hash=sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6 \
--hash=sha256:702c436735fbe99d59ada02a1f65cfc0d31c0ee8b7290912f8fbc5cd1e4b16c3 \
--hash=sha256:716ff8ec22f20b4d988b12884086bcef0fc99737043e503f7a3935a6be99b1ea \
--hash=sha256:762f99479dcb369f60ab9017ad4ab97a36a1dd7c1ee5a3b15db0f4b8659120cd \
--hash=sha256:7762faa47e8ff7eb80bd261d9a7d8eea2d8baa69de5e95b70c1f338bbe712f02 \
--hash=sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde \
--hash=sha256:799416bae98336e400981ff6e532d67d5c709cfb30afb79865a1315f94b0e224 \
--hash=sha256:7d034dcffa09e9a46c93fa3a3be402096cb5354ac6e41ab8e5cc9cd8b642ad76 \
--hash=sha256:7d28dff1db6764108bc30788d85d61c876beff416d9a49cb9dd7c5a9f34f5804 \
--hash=sha256:7d3538f9c0e50670f4deb93dbb696576e60590369cae2faf7de681e597a8a1f1 \
--hash=sha256:7d5980a3433d4b71a5e120f9dd551403d7824e31e2e67124fe2769c404c06913 \
--hash=sha256:7ea6b3e2c4250ff1de21c630fe72d0f63eb95c2c32ffbf64a358cf4a8836d714 \
--hash=sha256:86cf8755a791f72c85dc287128cc62d4f24d392e3f1e15837245623f4a33cccc \
--hash=sha256:88023dfe18799507b73f1dbb0d14326a17465de1bc9c9c7655c22845e9ddc3a2 \
--hash=sha256:89095c1968b4ba8285840e131bf2891b09ae137fe2146905acae0354fbce1b5e \
--hash=sha256:8d35c139744adb3e727cd51b1a18324bbe44b8bd41bf8322bca4d41289f48eda \
--hash=sha256:8e74a6135550c4748af665b1b1118b6aab33b1fc6a16f9aff630af107c3b4512 \
--hash=sha256:8f9ec95b8a043d3dfbc74d9abc6f7baf524dd27a8dc160b0a32ff9cdab650c28 \
--hash=sha256:90bec57cf82089383bd06a605b3eb8daebf7e5a668520beaf6e327a83a947699 \
--hash=sha256:95f2954c2c9473d892eca6e0409f3568b37ab62a8eedb122461f73cc273476e3 \
--hash=sha256:961be50688f7fba2fa65f63712d3b9b341a22311f5253460ce933f52f0de1c8c \
--hash=sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe \
--hash=sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a \
--hash=sha256:9d72af0cf10a76a600a9690078fe31c63b9588c8e86bf9fd353f713c84b5db0f \
--hash=sha256:9d8272c0e483b024e1b9ad029821470ed8ec65631dbd90217469da0e7cd89f1c \
--hash=sha256:a016194dbe13d14ee9556e734b772d8d67b947092b268d757fd4290e3ba2dfc2 \
--hash=sha256:a5781494d4d400a3f47f8f1da94b324f6e6b440a53387774002890a2a2f4b50f \
--hash=sha256:a95b05f9baf29b91171b3a8bd2020b028835243e7b0ff6bb23e2a3c228518b1b \
--hash=sha256:aa7a1b53a2a4452ada2d1b5dade9960b2522f1e61293a811a077439e39029565 \
--hash=sha256:ac0f1a2d0cfa7eea3f2aaf006ab6e70e8feeb16b75d65b7e5939982ca2f11056 \
--hash=sha256:af5e2915d41fe6c961694d7bfdc8562942638200f3ce2765dfb8b745cf997629 \
--hash=sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7 \
--hash=sha256:b65f590ef2a44640f9a05dbb548a429b4ade77913ce683ac8b1480777658a6c0 \
--hash=sha256:ba00f661f8ba35d075c937174e27c2c421cec3942fd2e0ea3e66996757c0fdd9 \
--hash=sha256:bccbbb5ee76a61f9d99b5bf3846a51d7fca4b6a732fe46f89295610edaf41853 \
--hash=sha256:bf01d8c84cbea96b944c73b22182e6c7c432b3475632b8111dbfdc95ddad6e13 \
--hash=sha256:bf5c6cf48238b0eb4c086978c492ad1cbc22373fc5b2d7353b3a598ce6db887a \
--hash=sha256:c16914df9fb7f500e440e6875fa23ff5e0b31db01fa9c06af98d59a91f0dc2e4 \
--hash=sha256:c351efb95e832a853a29361675f33a7ce53de1a109cd73fd47af0712213aa4ce \
--hash=sha256:c4165821e131d6d4ca444347c2b694e2311bcfa3fe5a861cc72968f28867beac \
--hash=sha256:c5f5df567f6eb216de69be06ce55c8b714090fae02b18a3b40da8163b8c5fa9c \
--hash=sha256:c941bb58d5a6e1c3892d86e42927ed6c180302f07e6d395d08c416e594b98b46 \
--hash=sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384 \
--hash=sha256:cb96698e3c7413d906ce83f8ffd245ec1bd94707541f299d0ce4d6b0193e982b \
--hash=sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210 \
--hash=sha256:cdf2448aab5f661c9315308ec8b93f4e8a1a67a3c733f8631067a2b67d5913dc \
--hash=sha256:d2117334c3af3bdcb9a88522b844a2bdb5efdc4f71c6c822df55486ae1c3347a \
--hash=sha256:d53d10f7da99ae46f7373b9150393e9c5eab9b224909982b43832668de4779f5 \
--hash=sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7 \
--hash=sha256:db3eb7d46527159a878ec3460e9d40615bc25ba337d477db681aea6e4f05c5d2 \
--hash=sha256:dbf7c7a88e2bac086f06d14577332760bdeecc42bdec8ac4077f6260557d9326 \
--hash=sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f \
--hash=sha256:df92f2aba50eb4d96718b68ef76f2e57a57b54f2fa62333496d16c6d585a85ca \
--hash=sha256:eb4e8997a49aa2c08a3e43c9045d224448b8941d88e7ac163c7d383e560cbf98 \
--hash=sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9 \
--hash=sha256:f146d154428a2523f9cc7936c02353c2459b8f6cf07d3cd1ee1c0a611109c5d5 \
--hash=sha256:f5bce581e6b8c235e566a14768a943b172ada3ed73537bb0c0be1edee312d4e7 \
--hash=sha256:f9912624a0c0b834b7520d7769b3644453aabc0a7e1c839da7359f050750e9bc \
--hash=sha256:fb62edb5bb52cca65fab91a63afa7561607120d26090a7e8fda6fb9f064726da \
--hash=sha256:ff067a8d8d880e7809e4ac88eb009bb848870115317b306666502ccad30b147f
# via weasyprint
cfgv==3.5.0 \
--hash=sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0 \
--hash=sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132
# via pre-commit
cssselect2==0.9.0 \
--hash=sha256:6a99e5f91f9a016a304dd929b0966ca464bcfda15177b6fb4a118fc0fb5d9563 \
--hash=sha256:759aa22c216326356f65e62e791d66160a0f9c91d1424e8d8adc5e74dddfc6fb
# via weasyprint
distlib==0.4.3 \
--hash=sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b \
--hash=sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed
# via virtualenv
filelock==3.30.0 \
--hash=sha256:1774e682dbe443bd60f9609162fc596e2c80dc84ffc2957068953406d0520090 \
--hash=sha256:40632998f0772e64183bb819f086a1b9def6be1090cf1dcb9d45f46806ef279b
# via
# python-discovery
# virtualenv
fonttools==4.63.0 \
--hash=sha256:032038247a96c1690f9f31e377c389383c902531b085aa4e4dabd6f57f870e69 \
--hash=sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c \
--hash=sha256:0c18358a155d75034911c5ee397a5b44cd19dd325dbb8b35fb60bf421d6a72ac \
--hash=sha256:0eac00b9118c3c2f87d272e45341871c5b3066baa3c86897fa634a7c3fb59096 \
--hash=sha256:1e874792a8212b44583ea02189d9e693906b2f78b261f372f95d6c563210ac1d \
--hash=sha256:22135da48a348785c5e2d5d2d9d6bec5ed44adacbaeb9db12d9493bf6c6bfa68 \
--hash=sha256:22693918177bd9ceabec4736d338045f357769416fc6b0b2508eefef75b08616 \
--hash=sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78 \
--hash=sha256:2b8ae05d9eacf6081414d759c0a352769ac28ce31280d6bb8e77b03f9e3c449f \
--hash=sha256:2c14b4fd138c4bafcca294765c547914e1aa431ae1ca94ab99d8db08c958bd3b \
--hash=sha256:308f957cdeaf8abe4e5f2f124902ef405448af92c90f80e302a3b771c2e6116b \
--hash=sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02 \
--hash=sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d \
--hash=sha256:51394295f1a51de8b5f30bdb1e1b9a4231536c7064ef5c6e211eec19fa36036f \
--hash=sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8 \
--hash=sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272 \
--hash=sha256:6b2248c5decb223562f7902ff6325077a073f608ee8e33e88ad88db734eb9f49 \
--hash=sha256:6d4741eb179121cab9eea4cb2393d24492373a260d7945006358c08cfbf45419 \
--hash=sha256:6db5140a60a5d731d21ec076745b40a310607731b0a565b50776393188649001 \
--hash=sha256:6e528da43bc3791085f8cb6141b1d13e459226790240340fcbb4625649238b03 \
--hash=sha256:796f27556dbe094c4824f75ca85267e4df776c79036c8441469a4df37038c196 \
--hash=sha256:79cdc9f567aec74a72918fd060283911406750cbc9fd28c1316023deb6ce31a9 \
--hash=sha256:7d76edbff9014094dbf03bd2d074709dfa6ec7aba13d838c937a2b33d2d6a86e \
--hash=sha256:7d782fac32985914c351556f68ac0855391572bcd87de50e05970d3cd4c96fc5 \
--hash=sha256:7dd683fef0663e9f0f45cf541d788d24caa3ec9db50796b588e1757d8b3bc007 \
--hash=sha256:85be818f5506e8a7753153def2c9550178f0ecae6a47b5e0e8dbb23f7cc90380 \
--hash=sha256:948428a275741f0b64b113c955425a953314f4b9ab9997f73a72c83e68e569c8 \
--hash=sha256:9ced0bd02ac751dd6319b0da88aaef24414e3b0dbc32bb4f24944821a3741a27 \
--hash=sha256:9e12f105d2b6342c559c298afb674006bb2893afc7102dcf8a1b55b0486b4e40 \
--hash=sha256:a8b33a82979e0a6a34ff435cc81317be1f95ec1ebb7a3a2d1c8a6a54f02ae44e \
--hash=sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0 \
--hash=sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263 \
--hash=sha256:afefc1ed0a59785a7fb06ea7e1678e849c193e1e387db783579bc7b3056fcfcb \
--hash=sha256:b1cd75a03ad8cb5bc40c90bfde68c0c47de423aa19e5c0f362b43520645eea94 \
--hash=sha256:ba04cb5891d4c0c21b6da95eda8d7b090021508a294fff33464fc7d241e0856b \
--hash=sha256:bf00f21eb5fb721dbaf73d1e9da6d02a1af7768f2ebcf9798be98beab8ba90f6 \
--hash=sha256:c0425b277a59cff3d80ca42162a8de360f318438a2ac83570842a678d826d579 \
--hash=sha256:c1aaa4b9c75798400ac043ce04d74e7830376c85095a5a6ed7cba2f17a266bf4 \
--hash=sha256:c2a2a42198b696a6f48fad91709afb55176e66a5e566131219dba372fb7f8c59 \
--hash=sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0 \
--hash=sha256:cb014d58140a38135f16064c74c652ed57aa0b75cbf8bb59cac821f7edb5334e \
--hash=sha256:ccf41f2efdf56994d22d73bef4ced1052161958169428d06ba9724ea9e9a64be \
--hash=sha256:cd7e9857e5e63738b9d9fd707bc1f59c8b09e5177726d23664db393c59bb08bd \
--hash=sha256:d76ac49f929aecaf82d83250b8347e099d7aecba0f4726c1d9b6df3b8bb5fe18 \
--hash=sha256:d7e5c9973aa04c95650c96e5f5ad865fbf42d62079163ecfab1e01cbc2504c22 \
--hash=sha256:dcf076a4474fe0d7367e5bbf5b052c7284fa1feca729c04176ce513521afd8a0 \
--hash=sha256:e3297a6a4059b4acc3a1e9a8b04741f240a80044eef08ebd32e8b5bcdddce75b \
--hash=sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b \
--hash=sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af \
--hash=sha256:fd1e3094f42d806d3d7c79162fc59e5910fcbe3a7360c385b8da969bc4493745
# via weasyprint
identify==2.6.19 \
--hash=sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a \
--hash=sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842
# via pre-commit
nodeenv==1.10.0 \
--hash=sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827 \
--hash=sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb
# via pre-commit
numpy==2.4.6 \
--hash=sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1 \
--hash=sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4 \
--hash=sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f \
--hash=sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079 \
--hash=sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096 \
--hash=sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47 \
--hash=sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66 \
--hash=sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d \
--hash=sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1 \
--hash=sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e \
--hash=sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147 \
--hash=sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd \
--hash=sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75 \
--hash=sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063 \
--hash=sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73 \
--hash=sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab \
--hash=sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4 \
--hash=sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41 \
--hash=sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402 \
--hash=sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698 \
--hash=sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7 \
--hash=sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8 \
--hash=sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b \
--hash=sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8 \
--hash=sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0 \
--hash=sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662 \
--hash=sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91 \
--hash=sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0 \
--hash=sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f \
--hash=sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3 \
--hash=sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f \
--hash=sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67 \
--hash=sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6 \
--hash=sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997 \
--hash=sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b \
--hash=sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e \
--hash=sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538 \
--hash=sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627 \
--hash=sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93 \
--hash=sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02 \
--hash=sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853 \
--hash=sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c \
--hash=sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43 \
--hash=sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd \
--hash=sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8 \
--hash=sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089 \
--hash=sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778 \
--hash=sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1 \
--hash=sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb \
--hash=sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261 \
--hash=sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb \
--hash=sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a \
--hash=sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8 \
--hash=sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359 \
--hash=sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5 \
--hash=sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7 \
--hash=sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751 \
--hash=sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8 \
--hash=sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605 \
--hash=sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e \
--hash=sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45 \
--hash=sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2 \
--hash=sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895 \
--hash=sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe \
--hash=sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb \
--hash=sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a \
--hash=sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577 \
--hash=sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d \
--hash=sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a \
--hash=sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda \
--hash=sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6 \
--hash=sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20
# via opencv-python-headless
opencv-python-headless==5.0.0.93 \
--hash=sha256:030ca5e0837a2963ab36ef896baa9767eb8d2b83353fb28af5a521e40dd8756f \
--hash=sha256:09a872a157c1376ab922a69bbf22f9a95bcc7b658a9d8b436a60212b02b2eeb4 \
--hash=sha256:10818d91510e05c04568ae12b5cd120779c70c01bf897b001a6221fe430df80f \
--hash=sha256:1e55af3abfb462eeeabe5c775f12bdb36216d8a93a3583d69e6bd6e1d6ba7d00 \
--hash=sha256:829717b6a95554f273e49e357cee3b3a2a26b6f4842fbc1bed2b45bdd8f87e0e \
--hash=sha256:840bd717c21e5c11cadadc022a823315ea417f961213d06b4df010e019eb16f4 \
--hash=sha256:b82f9831daab90b725c7c1ee1b36cb5732c367096ac76d119e64e14eb70d5f3c \
--hash=sha256:c6bcd96b185975ea240d22cfdb15a1f6d080cc95264cfbe2621f21bb144d89b9 \
--hash=sha256:ed709fdf9aa0bd1f2ed8549e71d19449b03a675bb581eb292285f6861953be37
# via -r .github/scripts/requirements_dev.in
pdf2image==1.17.0 \
--hash=sha256:eaa959bc116b420dd7ec415fcae49b98100dda3dd18cd2fdfa86d09f112f6d57 \
--hash=sha256:ecdd58d7afb810dffe21ef2b1bbc057ef434dabbac6c33778a38a3f7744a27e2
# via -r .github/scripts/requirements_dev.in
pillow==12.3.0 \
--hash=sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756 \
--hash=sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a \
--hash=sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59 \
--hash=sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45 \
--hash=sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3 \
--hash=sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df \
--hash=sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139 \
--hash=sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b \
--hash=sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39 \
--hash=sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e \
--hash=sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8 \
--hash=sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1 \
--hash=sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8 \
--hash=sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89 \
--hash=sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5 \
--hash=sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130 \
--hash=sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd \
--hash=sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d \
--hash=sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b \
--hash=sha256:25b9b82bb22e6e2b3cd07b39c68b7b862001226cb3dff7130d1cb914121b39ed \
--hash=sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace \
--hash=sha256:300557495eb45ebb8aec96c2da9c4be642fbf7cd937278b4013ba894ea8eb0eb \
--hash=sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931 \
--hash=sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510 \
--hash=sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6 \
--hash=sha256:37dc8f7bbb66efe481bb60defacef820c950c24713fb44962ed6aa2a50966de1 \
--hash=sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce \
--hash=sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385 \
--hash=sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e \
--hash=sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c \
--hash=sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7 \
--hash=sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace \
--hash=sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c \
--hash=sha256:514435a37670e3e5e08f3945b68718b6ed329bb84367777e16f9f4dfe1e61a0f \
--hash=sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64 \
--hash=sha256:5594fc43d548a7ed94949d139aa1341b270f1863f11cfd37f5a6c8b778a6b67f \
--hash=sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a \
--hash=sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827 \
--hash=sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17 \
--hash=sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4 \
--hash=sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a \
--hash=sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701 \
--hash=sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e \
--hash=sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91 \
--hash=sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66 \
--hash=sha256:85f998ea1848bc6757289e739cfbdda3a04adfd58b02fc018ce54d754a5ce468 \
--hash=sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217 \
--hash=sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658 \
--hash=sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418 \
--hash=sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a \
--hash=sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c \
--hash=sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330 \
--hash=sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402 \
--hash=sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09 \
--hash=sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930 \
--hash=sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f \
--hash=sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec \
--hash=sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a \
--hash=sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94 \
--hash=sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468 \
--hash=sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b \
--hash=sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965 \
--hash=sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8 \
--hash=sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd \
--hash=sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7 \
--hash=sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c \
--hash=sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777 \
--hash=sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35 \
--hash=sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9 \
--hash=sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f \
--hash=sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f \
--hash=sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0 \
--hash=sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c \
--hash=sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71 \
--hash=sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3 \
--hash=sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838 \
--hash=sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf \
--hash=sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321 \
--hash=sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26 \
--hash=sha256:f0606c8bf2cdefea14a43530f7657cbbb7ecf1c4222512492ef4a4434a9501ec \
--hash=sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9 \
--hash=sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65 \
--hash=sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5 \
--hash=sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e \
--hash=sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d \
--hash=sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198 \
--hash=sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7
# via
# -r .github/scripts/requirements_dev.in
# pdf2image
# weasyprint
platformdirs==4.10.0 \
--hash=sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7 \
--hash=sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a
# via
# python-discovery
# virtualenv
pre-commit==4.6.0 \
--hash=sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9 \
--hash=sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b
# via -r .github/scripts/requirements_dev.in
pycparser==3.0 \
--hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \
--hash=sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992
# via cffi
pydyf==0.12.1 \
--hash=sha256:ea25b4e1fe7911195cb57067560daaa266639184e8335365cc3ee5214e7eaadc \
--hash=sha256:fbd7e759541ac725c29c506612003de393249b94310ea78ae44cb1d04b220095
# via weasyprint
pyphen==0.17.2 \
--hash=sha256:3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd \
--hash=sha256:f60647a9c9b30ec6c59910097af82bc5dd2d36576b918e44148d8b07ef3b4aa3
# via weasyprint
python-discovery==1.4.4 \
--hash=sha256:5cad33982d412c1f3ffb8f9ca4ea292c9680bca3942451d30b69c37fce53a4a3 \
--hash=sha256:abebe9120b43453b68c908acfb1e72a19d1a959ed2cb620ad38fc57d08056dbe
# via virtualenv
pyyaml==6.0.3 \
--hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \
--hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a \
--hash=sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3 \
--hash=sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956 \
--hash=sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 \
--hash=sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c \
--hash=sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65 \
--hash=sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a \
--hash=sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0 \
--hash=sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b \
--hash=sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1 \
--hash=sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6 \
--hash=sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7 \
--hash=sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e \
--hash=sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007 \
--hash=sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310 \
--hash=sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4 \
--hash=sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9 \
--hash=sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295 \
--hash=sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea \
--hash=sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0 \
--hash=sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e \
--hash=sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac \
--hash=sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9 \
--hash=sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7 \
--hash=sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35 \
--hash=sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb \
--hash=sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b \
--hash=sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69 \
--hash=sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5 \
--hash=sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b \
--hash=sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c \
--hash=sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369 \
--hash=sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd \
--hash=sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824 \
--hash=sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198 \
--hash=sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065 \
--hash=sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c \
--hash=sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c \
--hash=sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764 \
--hash=sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196 \
--hash=sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b \
--hash=sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00 \
--hash=sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac \
--hash=sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8 \
--hash=sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e \
--hash=sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28 \
--hash=sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3 \
--hash=sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5 \
--hash=sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4 \
--hash=sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b \
--hash=sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf \
--hash=sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5 \
--hash=sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702 \
--hash=sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8 \
--hash=sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788 \
--hash=sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da \
--hash=sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d \
--hash=sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc \
--hash=sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c \
--hash=sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba \
--hash=sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f \
--hash=sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917 \
--hash=sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5 \
--hash=sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26 \
--hash=sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f \
--hash=sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b \
--hash=sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be \
--hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c \
--hash=sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3 \
--hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 \
--hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \
--hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0
# via pre-commit
tinycss2==1.5.1 \
--hash=sha256:3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661 \
--hash=sha256:d339d2b616ba90ccce58da8495a78f46e55d4d25f9fd71dfd526f07e7d53f957
# via
# cssselect2
# weasyprint
tinyhtml5==2.1.0 \
--hash=sha256:60a50ec3d938a37e491efa01af895853060943dcebb5627de5b10d188b338a67 \
--hash=sha256:6e11cfff38515834268daf89d5f85bbde0b6dd02e8d9e212d1385c2289b89f0a
# via weasyprint
unoserver==3.7 \
--hash=sha256:b05f9578506ac7374ae1b314c3a79528636c542ac78220a9ce99110584ca424b \
--hash=sha256:fc44e6808071c9d2957e705ecf1742cea8a582aa5d5cc23babf36bb332ec6e8e
# via -r .github/scripts/requirements_dev.in
virtualenv==21.6.1 \
--hash=sha256:15f978b7cd329f24855ff4a0c4b4899cc7678589f49adbdcbbb4d3232e641128 \
--hash=sha256:afe991df855715a2b2f60edfcc0107ef95a79fdfd8cb4cdaa71603d1c12e463b
# via pre-commit
weasyprint==69.0 \
--hash=sha256:475951cfd917014de6d4d005caff48c6aa867e7e42b80cd5b16a0484a1609ee6 \
--hash=sha256:a7a32f39ca16bd82ef11de99c92ea4b5f14951c9033af035e451ce4f4ee0a88c
# via -r .github/scripts/requirements_dev.in
webencodings==0.5.1 \
--hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
--hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
# via
# cssselect2
# tinycss2
# tinyhtml5
zopfli==0.4.3 \
--hash=sha256:0087c9a6f0c8a052be0f6d1a9bb71b6caffdd3e10201d6d6166e28d482cebe6d \
--hash=sha256:47604eee5c6704bdf0e94d8391fe3b74ddb2abd84128fbcfdc3ee0fc265feaef \
--hash=sha256:62248dbf8dbcbd588ee194b210e5be9fa80bce29641f55599d6d394bd2a9d8a3 \
--hash=sha256:628c3e941752880b3491db8d44163d0aedb221944e22a17187ff7fc549b050f6 \
--hash=sha256:769875152d0625c46707bcca57d4b2233fe653482067acd55fbf6ec525cb9bdc \
--hash=sha256:7e9703ca6e7ef66c8d05e0826b6f558b680c9db8206f84f05a3ee93430a12e42 \
--hash=sha256:7fa3c35193475290e3f007bbcdebdbae64ba2f012d75c632da0d727e1da50d5e \
--hash=sha256:88f4fbe429aad72bc206275d81fab11a097e0f951a5848d1f51083c37ea73073 \
--hash=sha256:921c2c9907f4364963848da5ad194b46d68865e07fdb975d04fd09bc42d47357 \
--hash=sha256:d3a50f91a13cea9bafe025de8fd87a005eb26de02a4f0c193127ddbf23ac8ebe \
--hash=sha256:d4f51dd1ab5312e837e2091284e0d9f1a138188f2e65812f9a5799dc02c45f94 \
--hash=sha256:eb0c9c1d40a8cb1d58762d7e57290ccb753e0828c4d01be8acb59aae5d0ca206 \
--hash=sha256:f2e0adcf7d36c6fd0dd36cc771ef7f0c5803a05666feafcd90d7170174a4148e
# via fonttools
# The following packages are considered to be unsafe in a requirements file:
pip==26.1.2 \
--hash=sha256:382ff9f685ee3bc25864f820aa50505825f10f5458ffff07e30a6d96e5715cab \
--hash=sha256:f49cd134c61cf2fd75e0ce2676db03e4054504a5a4986d00f8299ae632dc4605
# via -r .github/scripts/requirements_dev.in
setuptools==83.0.0 \
--hash=sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef \
--hash=sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3
# via -r .github/scripts/requirements_dev.in
@@ -1,2 +0,0 @@
tomlkit
tomli-w
@@ -1,14 +0,0 @@
#
# This file is autogenerated by pip-compile with Python 3.13
# by the following command:
#
# pip-compile --generate-hashes --output-file='.github\scripts\requirements_sync_readme.txt' --strip-extras '.github\scripts\requirements_sync_readme.in'
#
tomli-w==1.2.0 \
--hash=sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90 \
--hash=sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021
# via -r .github/scripts/requirements_sync_readme.in
tomlkit==0.15.0 \
--hash=sha256:4dbc8f0fc024412b57ced8757ac7461305126a648ff8c2c807fcb8e133a78738 \
--hash=sha256:7d1a9ecba3086638211b13814ea79c90dd54dd11993564376f3aa92271f5c7a3
# via -r .github/scripts/requirements_sync_readme.in
+6 -5
View File
@@ -4,17 +4,18 @@
Usage: verify-updater-signatures.py <dir-to-scan> [tauri.conf.json]
"""
import binascii
import sys
import json
import base64
import binascii
import hashlib
import json
import sys
from pathlib import Path
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
ART_ROOT = Path(sys.argv[1])
CONF = Path(sys.argv[2] if len(sys.argv) > 2 else "frontend/src-tauri/tauri.conf.json")
CONF = Path(sys.argv[2] if len(sys.argv) > 2 else "frontend/editor/src-tauri/tauri.conf.json")
def load_pubkey():
+2 -2
View File
@@ -86,7 +86,7 @@ jobs:
fi
fi
else
auth_users=("Frooodle" "sf298" "Ludy87" "LaserKaspar" "sbplat" "reecebrowne" "DarioGii" "ConnorYoh" "EthanHealy01" "jbrunton96" "balazs-szucs")
auth_users=("Frooodle" "sf298" "Ludy87" "LaserKaspar" "sbplat" "reecebrowne" "ConnorYoh" "EthanHealy01" "jbrunton96" "balazs-szucs")
is_auth=false; for u in "${auth_users[@]}"; do [ "$u" = "$PR_AUTHOR" ] && is_auth=true && break; done
if [ "$is_auth" = true ]; then
should=true
@@ -193,7 +193,7 @@ jobs:
echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
- name: Login to Docker Hub
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
@@ -54,7 +54,6 @@ jobs:
github.event.comment.user.login == 'Ludy87' ||
github.event.comment.user.login == 'balazs-szucs' ||
github.event.comment.user.login == 'reecebrowne' ||
github.event.comment.user.login == 'DarioGii' ||
github.event.comment.user.login == 'EthanHealy01' ||
github.event.comment.user.login == 'jbrunton96' ||
github.event.comment.user.login == 'ConnorYoh'
@@ -206,7 +205,7 @@ jobs:
token: ${{ steps.setup-bot.outputs.token }}
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
@@ -242,7 +241,7 @@ jobs:
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Login to Docker Hub
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
@@ -265,7 +264,7 @@ jobs:
if: needs.check-comment.outputs.enable_prototypes == 'true'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: ./engine
context: .
file: ./engine/Dockerfile
push: true
cache-from: type=gha,scope=stirling-pdf-engine
+37
View File
@@ -31,6 +31,9 @@ jobs:
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
enable-cache: true
cache-dependency-glob: |
engine/pyproject.toml
engine/uv.lock
cache-suffix: ai-engine
- name: Install Task
@@ -80,6 +83,32 @@ jobs:
});
}
- name: Run engine tests with coverage
id: engine-coverage
if: always()
run: task engine:test:coverage
- name: Add engine coverage to step summary
if: always() && steps.engine-coverage.outcome == 'success'
working-directory: engine
run: |
{
echo '## AI Engine coverage'
echo
echo '```text'
uv run coverage report --show-missing
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload engine coverage report
if: always() && steps.engine-coverage.outcome == 'success'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ai-engine-coverage
path: engine/coverage/
retention-days: 7
if-no-files-found: warn
- name: Fail if engine check failed
if: steps.engine-check.outcome == 'failure'
run: |
@@ -96,6 +125,14 @@ jobs:
echo "============================================"
exit 1
- name: Build engine production image
if: always()
run: docker build --file engine/Dockerfile --tag stirling-pdf-engine:ci .
- name: Build engine development image
if: always()
run: docker build --file engine/Dockerfile.dev --tag stirling-pdf-engine-dev:ci .
- name: Remove engine check comment on success
if: steps.engine-check.outcome == 'success' && github.event_name == 'pull_request'
continue-on-error: true
+9 -7
View File
@@ -35,7 +35,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK ${{ matrix.jdk-version }}
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: ${{ matrix.jdk-version }}
distribution: "temurin"
@@ -192,12 +192,14 @@ jobs:
retention-days: 3
if-no-files-found: warn
- name: Install defusedxml for coverage summary
# coverage-summary.py parses JaCoCo XML through defusedxml to
# silence security scanners that pattern-match on the stdlib
# xml.etree.ElementTree.parse call.
- name: Install uv
if: always() && matrix.flavor == 'saas'
run: python -m pip install --quiet defusedxml
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
enable-cache: true
cache-dependency-glob: |
engine/pyproject.toml
engine/uv.lock
- name: JaCoCo coverage step summary
# Only the saas leg posts the JUnit summary - it's a strict
@@ -206,7 +208,7 @@ jobs:
# near-identical tables crowding out the aggregate report.
if: always() && matrix.flavor == 'saas'
run: |
python scripts/coverage-summary.py \
uv run --project engine --locked --group tools python scripts/coverage-summary.py \
--title "Backend JUnit coverage (JDK ${{ matrix.jdk-version }})" \
--jacoco "common=app/common/build/reports/jacoco/test/jacocoTestReport.xml" \
--jacoco "core=app/core/build/reports/jacoco/test/jacocoTestReport.xml" \
+11 -10
View File
@@ -56,7 +56,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
@@ -294,7 +294,7 @@ jobs:
if: always()
working-directory: frontend
run: >
npx tsx scripts/report-flaky-tests.mts
npx tsx editor/scripts/report-flaky-tests.mts
"${{ github.workspace }}/frontend/playwright-report/results-oauth.json"
"${{ github.workspace }}/frontend/playwright-report/results-saml.json"
"${{ github.workspace }}/frontend/playwright-report/results-feature.json"
@@ -329,15 +329,16 @@ jobs:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
python-version: "3.12"
cache: "pip"
cache-dependency-path: ./testing/cucumber/requirements.txt
enable-cache: true
cache-dependency-glob: |
engine/pyproject.toml
engine/uv.lock
- name: Install behave test deps
run: |
pip install --require-hashes --only-binary=:all: -r ./testing/cucumber/requirements.txt
uv sync --project engine --locked --group cucumber
- name: Build the multi-node image
working-directory: testing/compose
run: docker compose -f "$MN_COMPOSE" build
@@ -360,10 +361,10 @@ jobs:
- name: Run multi-node regression (implemented guarantees)
working-directory: testing/cucumber
# -e overrides behave.ini's exclusion of features/multinode; ~@known_gap skips any tracked-gap scenarios.
run: python -m behave features/multinode -e "features/enterprise" --tags="~@known_gap ~@destructive" --no-capture -f plain
run: uv run --project ../../engine --locked --group cucumber python -m behave features/multinode -e "features/enterprise" --tags="~@known_gap ~@destructive" --no-capture -f plain
- name: Run multi-node failover (destructive)
working-directory: testing/cucumber
run: python -m behave features/multinode -e "features/enterprise" --tags="@destructive ~@known_gap" --no-capture -f plain
run: uv run --project ../../engine --locked --group cucumber python -m behave features/multinode -e "features/enterprise" --tags="@destructive ~@known_gap" --no-capture -f plain
- name: Dump node logs on failure
if: failure()
working-directory: testing/compose
+1 -1
View File
@@ -73,7 +73,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
+4 -1
View File
@@ -34,10 +34,13 @@ jobs:
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
enable-cache: true
cache-dependency-glob: |
engine/pyproject.toml
engine/uv.lock
cache-suffix: generated-models
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
+1 -1
View File
@@ -22,7 +22,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
+13 -10
View File
@@ -6,7 +6,7 @@ on:
pull_request_target:
types: [opened, synchronize, reopened]
paths:
- "frontend/public/locales/*/translation.toml"
- "frontend/editor/public/locales/*/translation.toml"
- ".github/scripts/check_language_toml.py"
- ".github/workflows/check_toml.yml"
@@ -76,7 +76,7 @@ jobs:
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/public/locales/[a-zA-Z-]+/translation\.toml$' > changed_files.txt || echo "No matching TOML files found in PR"
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"
# Check if any files were found
if [ ! -s changed_files.txt ]; then
echo "No TOML translation files changed in this PR"
@@ -166,12 +166,12 @@ jobs:
// Determine reference file
let referenceFilePath;
if (changedFiles.includes("frontend/public/locales/en-US/translation.toml")) {
if (changedFiles.includes("frontend/editor/public/locales/en-US/translation.toml")) {
console.log("Using PR branch reference file.");
const { data: fileContent } = await github.rest.repos.getContent({
owner: prRepoOwner,
repo: prRepoName,
path: "frontend/public/locales/en-US/translation.toml",
path: "frontend/editor/public/locales/en-US/translation.toml",
ref: branch,
});
@@ -183,7 +183,7 @@ jobs:
const { data: fileContent } = await github.rest.repos.getContent({
owner: repoOwner,
repo: repoName,
path: "frontend/public/locales/en-US/translation.toml",
path: "frontend/editor/public/locales/en-US/translation.toml",
ref: "main",
});
@@ -195,20 +195,23 @@ jobs:
console.log(`Reference file path: ${referenceFilePath}`);
core.exportVariable("REFERENCE_FILE", referenceFilePath);
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
python-version: "3.12"
enable-cache: true
cache-dependency-glob: |
engine/pyproject.toml
engine/uv.lock
- name: Install Python dependencies
run: |
pip install --require-hashes --only-binary=:all: -r ./.github/scripts/requirements_sync_readme.txt
uv sync --project engine --locked --group tools
- name: Run Python script to check files
id: run-check
run: |
echo "Running Python script to check TOML files..."
python .github/scripts/check_language_toml.py \
uv run --project engine --locked --group tools python .github/scripts/check_language_toml.py \
--actor ${{ github.event.pull_request.user.login }} \
--reference-file "${REFERENCE_FILE}" \
--branch "pr-branch" \
+10 -12
View File
@@ -41,7 +41,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
@@ -57,15 +57,13 @@ jobs:
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
python-version: "3.12"
- name: Install defusedxml for coverage scripts
# Both coverage-summary.py and coverage-matrix.py parse JaCoCo
# XML through defusedxml - see the script headers for context.
run: python -m pip install --quiet defusedxml
enable-cache: true
cache-dependency-glob: |
engine/pyproject.toml
engine/uv.lock
# Pattern matches every artifact this PR's producers might upload:
# jacoco-exec-junit-jdk-25 (uploaded only by the saas
@@ -158,7 +156,7 @@ jobs:
# ("how much of the backend do real user flows cover?").
if: steps.inventory.outputs.found_e2e == 'true'
run: |
python scripts/coverage-summary.py \
uv run --project engine --locked --group tools python scripts/coverage-summary.py \
--title "Real user-flow backend coverage (e2e:live + cucumber)" \
--jacoco "merged=build/reports/jacoco/aggregate-e2e/jacocoTestReport.xml" \
--github-step-summary
@@ -169,7 +167,7 @@ jobs:
# is meaningless when one is a strict superset of the other.
if: steps.inventory.outputs.found_all == 'true'
run: |
python scripts/coverage-summary.py \
uv run --project engine --locked --group tools python scripts/coverage-summary.py \
--title "Combined backend coverage (JUnit + e2e:live + cucumber)" \
--jacoco "merged=build/reports/jacoco/aggregate-all/jacocoTestReport.xml" \
--github-step-summary
@@ -224,7 +222,7 @@ jobs:
# generated above) plus whichever frontend artifacts landed.
# Every input is optional; missing ones render as "-".
run: |
python scripts/coverage-matrix.py \
uv run --project engine --locked --group tools python scripts/coverage-matrix.py \
${{ steps.inventory.outputs.found_all == 'true' && '--jacoco-all build/reports/jacoco/aggregate-all/jacocoTestReport.xml' || '' }} \
${{ steps.inventory.outputs.found_e2e == 'true' && '--jacoco-e2e build/reports/jacoco/aggregate-e2e/jacocoTestReport.xml' || '' }} \
--vitest matrix-inputs/vitest/coverage-summary.json \
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: 25
distribution: temurin
+1 -1
View File
@@ -85,7 +85,7 @@ jobs:
fi
- name: Login to Docker Hub
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
+10 -15
View File
@@ -33,7 +33,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
@@ -69,16 +69,17 @@ jobs:
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 chmod +x /usr/local/bin/docker-compose
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
python-version: "3.12"
cache: "pip" # caching pip dependencies
cache-dependency-path: ./testing/cucumber/requirements.txt
enable-cache: true
cache-dependency-glob: |
engine/pyproject.toml
engine/uv.lock
- name: Pip requirements
- name: Install Cucumber and coverage dependencies
run: |
pip install --require-hashes --only-binary=:all: -r ./testing/cucumber/requirements.txt
uv sync --project engine --locked --group cucumber --group tools
- name: Extract JaCoCo agent for cucumber coverage
# Stages build/jacoco/jacocoagent.jar where the coverage override
@@ -121,16 +122,10 @@ jobs:
echo "report=false" >> "$GITHUB_OUTPUT"
fi
- name: Install defusedxml for coverage summary
# coverage-summary.py parses JaCoCo XML through defusedxml -
# see the script header for context.
if: always() && steps.cucumber-coverage.outputs.report == 'true'
run: python -m pip install --quiet defusedxml
- name: Cucumber coverage step summary
if: always() && steps.cucumber-coverage.outputs.report == 'true'
run: |
python scripts/coverage-summary.py \
uv run --project engine --locked --group tools python scripts/coverage-summary.py \
--title "Cucumber (docker) JaCoCo coverage" \
--jacoco "cucumber=build/reports/jacoco/cucumber/jacocoTestReport.xml" \
--github-step-summary
+18 -37
View File
@@ -21,7 +21,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
@@ -93,7 +93,7 @@ jobs:
# ::warning:: annotations + a job summary; never fails the job.
if: always()
working-directory: frontend
run: npx tsx scripts/report-flaky-tests.mts "$PLAYWRIGHT_JSON_OUTPUT_FILE"
run: npx tsx editor/scripts/report-flaky-tests.mts "$PLAYWRIGHT_JSON_OUTPUT_FILE"
env:
PLAYWRIGHT_JSON_OUTPUT_FILE: ${{ github.workspace }}/frontend/playwright-report/results.json
- name: Generate JaCoCo report from e2e:live .exec
@@ -119,20 +119,18 @@ jobs:
echo "::warning::No e2e:live .exec found at .test-state/playwright/jacoco.exec; skipping report"
echo "report=false" >> "$GITHUB_OUTPUT"
fi
- name: Set up Python for coverage summary
if: always() && steps.live-coverage.outputs.report == 'true'
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
- name: Install uv
if: always()
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
python-version: "3.12"
- name: Install defusedxml for coverage summary
# coverage-summary.py uses defusedxml instead of stdlib xml.etree
# to dodge XXE / billion-laughs scanner findings.
if: always() && steps.live-coverage.outputs.report == 'true'
run: python -m pip install --quiet defusedxml
enable-cache: true
cache-dependency-glob: |
engine/pyproject.toml
engine/uv.lock
- name: e2e:live coverage step summary
if: always() && steps.live-coverage.outputs.report == 'true'
run: |
python scripts/coverage-summary.py \
uv run --project engine --locked --group tools python scripts/coverage-summary.py \
--title "Playwright (live backend) JaCoCo coverage" \
--jacoco "e2e-live=build/reports/jacoco/e2e-live/jacocoTestReport.xml" \
--github-step-summary
@@ -155,23 +153,6 @@ jobs:
retention-days: 7
if-no-files-found: warn
- name: Set up Python for frontend coverage summary
# Separate from the backend-coverage python step because the
# frontend path doesn't depend on a JaCoCo report - it produces
# a summary even on backend failure, as long as some Playwright
# tests ran far enough to dump V8 coverage.
if: always()
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- name: Install defusedxml for frontend coverage summary
# Idempotent re-install: the backend-coverage step may have
# installed it already, but this leg can run on its own when the
# backend report step skips (e.g. .exec missing).
if: always()
run: python -m pip install --quiet defusedxml
- name: Aggregate Playwright frontend (V8) coverage
# Rolls per-test V8 dumps from the test-base fixture into one
# vitest-shaped coverage-summary.json. Tolerates a missing dump
@@ -182,7 +163,7 @@ jobs:
run: |
if [ -d .test-state/playwright/coverage-pw ] && \
find .test-state/playwright/coverage-pw -name '*.json' -type f | grep -q .; then
python scripts/playwright-coverage-summary.py \
uv run --project engine --locked --group tools python scripts/playwright-coverage-summary.py \
.test-state/playwright/coverage-pw \
--out .test-state/playwright/coverage-pw-summary/coverage-summary.json
echo "summary=true" >> "$GITHUB_OUTPUT"
@@ -194,10 +175,10 @@ jobs:
- name: Playwright frontend coverage step summary
if: always() && steps.pw-frontend-coverage.outputs.summary == 'true'
run: |
python scripts/coverage-summary.py \
--title "Playwright (live) frontend coverage" \
--vitest .test-state/playwright/coverage-pw-summary/coverage-summary.json \
--github-step-summary
uv run --project engine --locked --group tools python scripts/coverage-summary.py \
--title "Playwright (live) frontend coverage" \
--vitest .test-state/playwright/coverage-pw-summary/coverage-summary.json \
--github-step-summary
- name: Upload Playwright frontend coverage
# Bundle both the aggregated summary and the raw V8 dumps so
@@ -230,8 +211,8 @@ jobs:
run: |
echo "::group::Playwright output dirs"
# Playwright anchors its default outputDir + HTML report to the
# nearest package.json, which is frontend/ (frontend has
# none), so artifacts land under frontend/, not frontend/.
# nearest package.json, which is frontend/ (frontend/editor has
# none), so artifacts land under frontend/, not frontend/editor/.
ls -la frontend/playwright-report 2>/dev/null \
|| echo "no playwright-report at frontend/"
ls -la frontend/test-results 2>/dev/null \
@@ -246,7 +227,7 @@ jobs:
# test-results/ holds the per-test trace.zip (with browser console
# logs) + screenshots/video; playwright-report/ is the HTML report.
# Both live under frontend/ (Playwright anchors them to the nearest
# package.json, which is frontend/; frontend has none).
# package.json, which is frontend/; frontend/editor has none).
path: |
frontend/playwright-report/
frontend/test-results/
+20 -7
View File
@@ -2,7 +2,8 @@ name: Playwright E2E (stubbed)
# Reusable workflow called from build.yml. Backend-free Playwright suite —
# fast, no Spring Boot required. Runs against the `stubbed` project which
# mocks API responses in the browser.
# mocks API responses in the browser. Fans out one job per browser
# (chromium/firefox/webkit) so all three run in parallel on their own runner.
on:
workflow_call:
@@ -11,7 +12,19 @@ permissions:
jobs:
playwright-e2e:
name: playwright-e2e (${{ matrix.browser }})
runs-on: ubuntu-latest
strategy:
# One browser breaking must not mask a failure in another - report all.
fail-fast: false
matrix:
include:
- browser: chromium
project: stubbed
- browser: firefox
project: stubbed-firefox
- browser: webkit
project: stubbed-webkit
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
@@ -27,29 +40,29 @@ jobs:
cache-dependency-path: frontend/package-lock.json
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
- name: Install Playwright (chromium only)
run: task e2e:install -- chromium
- 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 (chromium)
- name: Run stubbed E2E tests (${{ matrix.browser }})
env:
PLAYWRIGHT_JSON_OUTPUT_FILE: ${{ github.workspace }}/frontend/playwright-report/results.json
run: task e2e:stubbed -- --workers=3
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)
# leaves the step green, so this is the only place it surfaces. Emits
# ::warning:: annotations + a job summary; never fails the job.
if: always()
working-directory: frontend
run: npx tsx scripts/report-flaky-tests.mts "$PLAYWRIGHT_JSON_OUTPUT_FILE"
run: npx tsx editor/scripts/report-flaky-tests.mts "$PLAYWRIGHT_JSON_OUTPUT_FILE"
env:
PLAYWRIGHT_JSON_OUTPUT_FILE: ${{ github.workspace }}/frontend/playwright-report/results.json
- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: playwright-report-stubbed-${{ github.run_id }}
name: playwright-report-stubbed-${{ matrix.browser }}-${{ github.run_id }}
path: frontend/playwright-report/
retention-days: 7
@@ -113,31 +113,31 @@ jobs:
NPM_CONFIG_IGNORE_SCRIPTS: "true"
working-directory: frontend
run: |
mkdir -p src/assets
npx --yes license-report --only=prod --output=json > src/assets/3rdPartyLicenses.json
mkdir -p editor/src/assets
npx --yes license-report --only=prod --output=json > editor/src/assets/3rdPartyLicenses.json
- name: Postprocess with project script (BASE version)
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
env:
PR_IS_FORK: "true"
run: |
node base/frontend/scripts/generate-licenses.js \
--input frontend/src/assets/3rdPartyLicenses.json
node base/frontend/editor/scripts/generate-licenses.js \
--input frontend/editor/src/assets/3rdPartyLicenses.json
- name: Copy postprocessed artifacts back (fork PRs)
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
run: |
mkdir -p frontend/src/assets
if [ -f "base/frontend/src/assets/3rdPartyLicenses.json" ]; then
cp base/frontend/src/assets/3rdPartyLicenses.json frontend/src/assets/3rdPartyLicenses.json
mkdir -p frontend/editor/src/assets
if [ -f "base/frontend/editor/src/assets/3rdPartyLicenses.json" ]; then
cp base/frontend/editor/src/assets/3rdPartyLicenses.json frontend/editor/src/assets/3rdPartyLicenses.json
fi
if [ -f "base/frontend/src/assets/license-warnings.json" ]; then
cp base/frontend/src/assets/license-warnings.json frontend/src/assets/license-warnings.json
if [ -f "base/frontend/editor/src/assets/license-warnings.json" ]; then
cp base/frontend/editor/src/assets/license-warnings.json frontend/editor/src/assets/license-warnings.json
fi
- name: Check for license warnings
run: |
if [ -f "frontend/src/assets/license-warnings.json" ]; then
if [ -f "frontend/editor/src/assets/license-warnings.json" ]; then
echo "LICENSE_WARNINGS_EXIST=true" >> $GITHUB_ENV
else
echo "LICENSE_WARNINGS_EXIST=false" >> $GITHUB_ENV
@@ -185,10 +185,10 @@ jobs:
echo ""
if [ "${LICENSE_WARNINGS_EXIST}" = "true" ]; then
echo "❌ **Failed** incompatible or unknown licenses found."
if [ -f "frontend/src/assets/license-warnings.json" ]; then
if [ -f "frontend/editor/src/assets/license-warnings.json" ]; then
echo ""
echo "### Warnings"
jq -r '.warnings[] | "- \(.message)"' frontend/src/assets/license-warnings.json || true
jq -r '.warnings[] | "- \(.message)"' frontend/editor/src/assets/license-warnings.json || true
fi
else
echo "✅ **Passed** no license warnings detected."
@@ -214,7 +214,7 @@ jobs:
const fs = require('fs');
let warningDetails = '';
try {
const warnings = JSON.parse(fs.readFileSync('frontend/src/assets/license-warnings.json', 'utf8'));
const warnings = JSON.parse(fs.readFileSync('frontend/editor/src/assets/license-warnings.json', 'utf8'));
warningDetails = warnings.warnings.map(w => `- ${w.message}`).join('\n');
} catch (e) {
warningDetails = 'Unable to read warning details';
@@ -254,7 +254,7 @@ jobs:
- name: Commit changes (Push only)
if: github.event_name == 'push'
run: |
git add frontend/src/assets/3rdPartyLicenses.json
git add frontend/editor/src/assets/3rdPartyLicenses.json
# Note: Do NOT commit license-warnings.json - it's only for PR review
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
@@ -272,7 +272,7 @@ jobs:
The following licenses may require review for corporate compatibility:
$(cat frontend/src/assets/license-warnings.json | jq -r '.warnings[].message')
$(cat frontend/editor/src/assets/license-warnings.json | jq -r '.warnings[].message')
Please review these licenses to ensure they are acceptable for your use case."
fi
@@ -345,7 +345,7 @@ jobs:
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
+12 -24
View File
@@ -1,8 +1,8 @@
name: Frontend lint, type-check, and build
# Reusable workflow called from build.yml when frontend / testing sources
# change. Runs the consolidated `task frontend:check:all` (lint, types,
# unit tests, build) and uploads the dist artifact for downstream jobs.
# change. Runs `task frontend:check:all` and uploads the
# coverage + dist artifacts for downstream jobs.
on:
workflow_call:
@@ -105,44 +105,32 @@ jobs:
comment_id: existing.id,
});
}
- name: Vitest coverage
# Separate from `frontend:check:all` so the quality-gate run stays
# uninstrumented (faster signal) and coverage stays an informational
# follow-up. Continue-on-error keeps the workflow green even when
# a handful of test files refuse to import (e.g. missing icon
# specifiers) - the summary still gets posted with whatever
# vitest managed to instrument.
id: frontend-coverage
continue-on-error: true
run: task frontend:test:coverage
- name: Set up Python for coverage summary
- name: Install uv
if: always()
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
python-version: "3.12"
- name: Install defusedxml for coverage summary
# See coverage-summary.py header - it parses XML through defusedxml
# to dodge the stdlib parser's exposure to XXE / billion-laughs.
if: always()
run: python -m pip install --quiet defusedxml
enable-cache: true
cache-dependency-glob: |
engine/pyproject.toml
engine/uv.lock
- name: Vitest coverage step summary
if: always()
run: |
python scripts/coverage-summary.py \
uv run --project engine --locked --group tools python scripts/coverage-summary.py \
--title "Frontend Vitest coverage" \
--vitest frontend/coverage/coverage-summary.json \
--vitest frontend/editor/coverage/coverage-summary.json \
--github-step-summary
- name: Upload vitest coverage report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: frontend-coverage
path: frontend/coverage/
path: frontend/editor/coverage/
retention-days: 7
if-no-files-found: warn
- name: Upload frontend build artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: frontend-build
path: frontend/dist/
path: frontend/editor/dist/
retention-days: 3
+22 -17
View File
@@ -52,7 +52,7 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
@@ -142,7 +142,7 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
@@ -240,7 +240,7 @@ jobs:
# before the second setup-java overwrites JAVA_HOME.
- name: Set up x86_64 JDK 25 (macOS universal JRE)
if: matrix.platform == 'macos-15'
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
@@ -252,7 +252,7 @@ jobs:
# Temurin has no windows-aarch64 JDK 25 yet; Microsoft OpenJDK does.
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: ${{ matrix.platform == 'windows-11-arm' && 'microsoft' || 'temurin' }}
@@ -441,7 +441,7 @@ jobs:
env:
KEYPAIR_ALIAS: ${{ secrets.SM_KEYPAIR_ALIAS }}
run: |
cat > ./frontend/src-tauri/tauri.windows.conf.json <<EOF
cat > ./frontend/editor/src-tauri/tauri.windows.conf.json <<EOF
{
"bundle": {
"windows": {
@@ -454,7 +454,7 @@ jobs:
}
EOF
echo "Generated tauri.windows.conf.json (alias masked):"
sed "s/${KEYPAIR_ALIAS}/***/g" ./frontend/src-tauri/tauri.windows.conf.json
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')
@@ -475,7 +475,7 @@ jobs:
fi
- name: Build Tauri app
uses: tauri-apps/tauri-action@84b9d35b5fc46c1e45415bdb6144030364f7ebc5 # v0.6.2
uses: tauri-apps/tauri-action@1deb371b0cd8bd54025b384f1cd735e725c4060f # v1.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
@@ -502,10 +502,9 @@ jobs:
SM_CODE_SIGNING_CERT_SHA1_HASH: ${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }}
CI: true
with:
projectPath: ./frontend
projectPath: ./frontend/editor
tauriScript: npx tauri
args: ${{ matrix.args }}
updaterJsonKeepUniversal: true
# Bundled libwayland conflicts with the host's on some distros (Fedora
# Wayland: EGL_BAD_PARAMETER, blank window - #6878). Repack without it,
@@ -522,7 +521,7 @@ jobs:
APPIMAGETOOL_SIGN_PASSPHRASE: ${{ secrets.RELEASE_GPG_PASSPHRASE }}
run: |
set -euo pipefail
AI=$(find "$PWD/frontend/src-tauri/target" -name "*.AppImage" | head -1)
AI=$(find "$PWD/frontend/editor/src-tauri/target" -name "*.AppImage" | head -1)
if [ -z "$AI" ]; then echo "No AppImage found - skipping"; exit 0; fi
chmod +x "$AI"
WORK=$(mktemp -d)
@@ -579,7 +578,7 @@ jobs:
# inner exe before packing and the setup exe after, so verifying the setup
# exe is the arm64 equivalent of the MSI + inner-exe check below.
if ("${{ matrix.platform }}" -eq "windows-11-arm") {
$setupExes = Get-ChildItem -Path "./frontend/src-tauri/target" -Filter "*-setup.exe" -Recurse -File
$setupExes = Get-ChildItem -Path "./frontend/editor/src-tauri/target" -Filter "*-setup.exe" -Recurse -File
if ($setupExes.Count -eq 0) {
Write-Host "[ERROR] No NSIS installer found under target/"
exit 1
@@ -599,7 +598,7 @@ jobs:
$allSigned = $true
# Check MSI installer (outer wrapper - what users download)
$msiFiles = Get-ChildItem -Path "./frontend/src-tauri/target" -Filter "*.msi" -Recurse -File
$msiFiles = Get-ChildItem -Path "./frontend/editor/src-tauri/target" -Filter "*.msi" -Recurse -File
if ($msiFiles.Count -eq 0) {
Write-Host "[ERROR] No MSI found under target/"
exit 1
@@ -682,7 +681,7 @@ jobs:
# Absolute dist path so the cd below can't break the copy targets.
DIST="$GITHUB_WORKSPACE/dist"
mkdir -p "$DIST"
cd ./frontend/src-tauri/target
cd ./frontend/editor/src-tauri/target
echo "=== tauri bundle artifacts ==="
find . -path "*/bundle/*" \( -name "*.msi" -o -name "*.deb" \
@@ -743,7 +742,7 @@ jobs:
with:
sparse-checkout: |
.github/scripts/verify-updater-signatures.py
frontend/src-tauri/tauri.conf.json
frontend/editor/src-tauri/tauri.conf.json
sparse-checkout-cone-mode: false
- name: Download all Tauri artifacts
@@ -888,11 +887,17 @@ jobs:
# Gate publish on valid updater sigs. Runs after the review upload (so
# artifacts survive for debugging) and before action-gh-release.
- 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: Verify updater signatures
run: |
python3 -m pip install --quiet 'cryptography==44.0.0'
python3 .github/scripts/verify-updater-signatures.py \
./artifacts/tauri frontend/src-tauri/tauri.conf.json
uv run --project engine --locked --only-group updater-signatures python .github/scripts/verify-updater-signatures.py \
./artifacts/tauri frontend/editor/src-tauri/tauri.conf.json
# workflow_dispatch path requires platform=='all' so a single-platform
# dispatch can't overwrite an existing release's full latest.json with a
+4 -3
View File
@@ -59,9 +59,10 @@ jobs:
# the story itself — a shared component, a theme token — still surfaces within
# a day.
a11y-all-stories:
name: a11y (every story)
name: a11y (every story, light + dark)
runs-on: ubuntu-latest
timeout-minutes: 60
# Two full sweeps (one per theme), each ~30 minutes of browser time.
timeout-minutes: 120
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
@@ -81,7 +82,7 @@ jobs:
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
- name: a11y gate (every story)
- name: a11y gate (every story, light + dark)
run: task frontend:storybook:a11y
- name: Upload scan reports
+3
View File
@@ -28,6 +28,9 @@ jobs:
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
enable-cache: true
cache-dependency-glob: |
engine/pyproject.toml
engine/uv.lock
cache-suffix: pre-commit
- name: Install Task
+4 -4
View File
@@ -50,13 +50,13 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Login to Docker Hub
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
- name: Login to GitHub Container Registry
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -67,7 +67,7 @@ jobs:
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
- name: Convert repository owner to lowercase
id: repoowner
@@ -75,7 +75,7 @@ jobs:
- name: Generate tags for base image
id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: |
${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf-base
+7 -7
View File
@@ -60,7 +60,7 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
@@ -103,20 +103,20 @@ jobs:
cosign-release: "v2.4.1"
- name: Login to Docker Hub
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
- name: Login to GitHub Container Registry
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
- name: Convert repository owner to lowercase
id: repoowner
@@ -125,7 +125,7 @@ jobs:
- name: Generate tags for latest
id: meta
if: env.RUN_MAIN_APP == 'true'
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: |
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
@@ -173,7 +173,7 @@ jobs:
- name: Generate tags for latest-fat
id: meta-fat
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
if: env.RUN_MAIN_APP == 'true' && github.ref != 'refs/heads/main' && github.ref != 'refs/heads/testMain'
with:
images: |
@@ -217,7 +217,7 @@ jobs:
- name: Generate tags for ultra-lite
id: meta-lite
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
if: env.RUN_MAIN_APP == 'true' && github.ref != 'refs/heads/main' && github.ref != 'refs/heads/testMain'
with:
images: |
+2 -2
View File
@@ -26,13 +26,13 @@ jobs:
uses: imjasonh/setup-crane@31b88afe9de28ae0ffa220711af4b60be9435f6e # v0.4
- name: Login to Docker Hub
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
- name: Login to GitHub Container Registry
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
+1 -1
View File
@@ -45,7 +45,7 @@ jobs:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
with:
results_file: results.sarif
results_format: sarif
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
+2 -2
View File
@@ -84,12 +84,12 @@ jobs:
body: |
Auto-generated by ${{ steps.setup-bot.outputs.app-slug }}[bot].
Regenerates `frontend/src/processor/proprietary/generated/docsManifest.json`
Regenerates `frontend/editor/src/portal/generated/docsManifest.json`
from the Stirling docs repo via `npm run docs:sync`.
labels: |
Documentation
github-actions
Front End
add-paths: frontend/src/processor/proprietary/generated/docsManifest.json
add-paths: frontend/editor/src/portal/generated/docsManifest.json
delete-branch: true
sign-commits: true
+13 -16
View File
@@ -12,7 +12,7 @@ on:
- "app/proprietary/build.gradle"
- "gradle/spotless.gradle"
- "README.md"
- "frontend/public/locales/*/translation.toml"
- "frontend/editor/public/locales/*/translation.toml"
- "app/core/src/main/resources/static/3rdPartyLicenses.json"
- "scripts/ignore_translation.toml"
@@ -51,28 +51,25 @@ jobs:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
cache: "pip" # caching pip dependencies
- name: Install Python dependencies
run: |
pip install --require-hashes --only-binary=:all: -r ./.github/scripts/requirements_sync_readme.txt
- 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
cache-suffix: sync-files
- name: Install Python dependencies
run: |
uv sync --project engine --locked --group tools
- name: Install Task
uses: go-task/setup-task@3be4020d41929789a01026e0e427a4321ce0ad44 # v2.0.0
- name: Sync translation TOML files
run: |
python .github/scripts/check_language_toml.py --reference-file "frontend/public/locales/en-US/translation.toml" --branch main
uv run --project engine --locked --group tools python .github/scripts/check_language_toml.py --reference-file "frontend/editor/public/locales/en-US/translation.toml" --branch main
- name: Sort translation TOML files
run: |
@@ -80,12 +77,12 @@ jobs:
- name: Commit translation files
run: |
git add frontend/public/locales/*/translation.toml
git add frontend/editor/public/locales/*/translation.toml
git diff --staged --quiet || git commit -m ":memo: Sync translation files (TOML)" || echo "No changes detected"
- name: Sync README.md
run: |
python scripts/counter_translation_v3.py
uv run --project engine --locked --group tools python scripts/counter_translation_v3.py
- name: Run git add
run: |
@@ -110,7 +107,7 @@ jobs:
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/public/locales/*/translation.toml`) to reflect changes in the reference file `en-US/translation.toml`.
- 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
@@ -135,5 +132,5 @@ jobs:
sign-commits: true
add-paths: |
README.md
frontend/public/locales/*/translation.toml
frontend/editor/public/locales/*/translation.toml
scripts/ignore_translation.toml
+15 -15
View File
@@ -152,7 +152,7 @@ jobs:
- name: Cache Rust build
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
workspaces: frontend/src-tauri
workspaces: frontend/editor/src-tauri
# Stable key shared across workflows so the nightly warmer.
# rust-cache still appends OS + rustc + Cargo.lock.
shared-key: tauri-${{ matrix.name }}
@@ -162,7 +162,7 @@ jobs:
- name: Set up x86_64 JDK 25 (macOS universal JRE)
if: matrix.platform == 'macos-15'
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
@@ -174,7 +174,7 @@ jobs:
# Temurin has no windows-aarch64 JDK 25 yet; Microsoft OpenJDK does.
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: ${{ matrix.platform == 'windows-11-arm' && 'microsoft' || 'temurin' }}
@@ -353,7 +353,7 @@ jobs:
env:
KEYPAIR_ALIAS: ${{ secrets.SM_KEYPAIR_ALIAS }}
run: |
cat > ./frontend/src-tauri/tauri.windows.conf.json <<EOF
cat > ./frontend/editor/src-tauri/tauri.windows.conf.json <<EOF
{
"bundle": {
"windows": {
@@ -386,7 +386,7 @@ jobs:
- name: Build Tauri app (signed)
if: env.SIGN_BUNDLE == 'true'
uses: tauri-apps/tauri-action@84b9d35b5fc46c1e45415bdb6144030364f7ebc5 # v0.6.2
uses: tauri-apps/tauri-action@1deb371b0cd8bd54025b384f1cd735e725c4060f # v1.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
@@ -412,7 +412,7 @@ jobs:
SM_CODE_SIGNING_CERT_SHA1_HASH: ${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }}
CI: true
with:
projectPath: ./frontend
projectPath: ./frontend/editor
tauriScript: npx tauri
# Linux: build deb+rpm only here (deb-only on minimal smoke builds).
# AppImage runs in its own continue-on-error step below so its
@@ -421,7 +421,7 @@ jobs:
- name: Build Tauri app (unsigned)
if: env.SIGN_BUNDLE != 'true'
uses: tauri-apps/tauri-action@84b9d35b5fc46c1e45415bdb6144030364f7ebc5 # v0.6.2
uses: tauri-apps/tauri-action@1deb371b0cd8bd54025b384f1cd735e725c4060f # v1.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SIGN: "0"
@@ -432,7 +432,7 @@ jobs:
VITE_SAAS_BACKEND_API_URL: ${{ secrets.VITE_SAAS_BACKEND_API_URL || 'https://api.stirlingpdf.com' }}
CI: true
with:
projectPath: ./frontend
projectPath: ./frontend/editor
tauriScript: npx tauri
# Linux: build deb+rpm only here (deb-only on minimal smoke builds).
# AppImage runs in its own continue-on-error step below so its
@@ -447,7 +447,7 @@ jobs:
- name: Build Tauri app (Linux AppImage)
if: matrix.platform == 'ubuntu-22.04' && !inputs.minimal
continue-on-error: true
uses: tauri-apps/tauri-action@84b9d35b5fc46c1e45415bdb6144030364f7ebc5 # v0.6.2
uses: tauri-apps/tauri-action@1deb371b0cd8bd54025b384f1cd735e725c4060f # v1.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SIGN: ${{ (inputs.sign && env.RELEASE_GPG_PRIVATE_KEY != '' && github.ref == 'refs/heads/main') && '1' || '0' }}
@@ -460,7 +460,7 @@ jobs:
VITE_SAAS_BACKEND_API_URL: ${{ secrets.VITE_SAAS_BACKEND_API_URL || 'https://api.stirlingpdf.com' }}
CI: true
with:
projectPath: ./frontend
projectPath: ./frontend/editor
tauriScript: npx tauri
args: --bundles appimage
@@ -472,7 +472,7 @@ jobs:
continue-on-error: true
run: |
set -euo pipefail
AI=$(find "$PWD/frontend/src-tauri/target" -name "*.AppImage" | head -1)
AI=$(find "$PWD/frontend/editor/src-tauri/target" -name "*.AppImage" | head -1)
if [ -z "$AI" ]; then echo "No AppImage found - skipping"; exit 0; fi
chmod +x "$AI"
WORK=$(mktemp -d)
@@ -509,7 +509,7 @@ jobs:
if: env.SIGN_BUNDLE == 'true' && matrix.platform == 'macos-15'
run: |
echo "🔍 Verifying notarization status..."
cd ./frontend/src-tauri/target
cd ./frontend/editor/src-tauri/target
DMG_FILE=$(find . -name "*.dmg" | head -1)
if [ -n "$DMG_FILE" ]; then
echo "Found DMG: $DMG_FILE"
@@ -526,7 +526,7 @@ jobs:
# Absolute dist path so the cd below can't break the copy targets.
DIST="$GITHUB_WORKSPACE/dist"
mkdir -p "$DIST"
cd ./frontend/src-tauri/target
cd ./frontend/editor/src-tauri/target
# Find and rename artifacts based on platform
if [ "${{ matrix.platform }}" = "windows-latest" ]; then
@@ -638,7 +638,7 @@ jobs:
- name: Verify build artifacts
shell: bash
run: |
cd ./frontend/src-tauri/target
cd ./frontend/editor/src-tauri/target
# Check for expected artifacts based on platform
if [ "${{ matrix.platform }}" = "windows-latest" ] || [ "${{ matrix.platform }}" = "windows-11-arm" ]; then
@@ -669,7 +669,7 @@ jobs:
- name: Test artifact sizes
shell: bash
run: |
cd ./frontend/src-tauri/target
cd ./frontend/editor/src-tauri/target
echo "Artifact sizes for ${{ matrix.name }}:"
find . -name "*.exe" -o -name "*.dmg" -o -name "*.deb" -o -name "*.rpm" -o -name "*.AppImage" -o -name "*.msi" | while read file; do
if [ -f "$file" ]; then
+4 -4
View File
@@ -61,7 +61,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Login to GitHub Container Registry
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -79,7 +79,7 @@ jobs:
echo "Disk space after cleanup:" && df -h
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
@@ -107,7 +107,7 @@ jobs:
STIRLING_PDF_DESKTOP_UI: false
- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
- name: Set up Docker Buildx
id: buildx
@@ -199,7 +199,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
- name: Set up Docker Buildx
id: buildx
+2 -2
View File
@@ -33,7 +33,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
@@ -67,7 +67,7 @@ jobs:
echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
- name: Login to Docker Hub
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
+5 -5
View File
@@ -24,7 +24,7 @@ configs/
watchedFolders/
# The rule above targets the app's runtime watched-folders working dir, but it
# also matches this frontend source component dir; keep the source tracked.
!frontend/src/editor/proprietary/components/watchedFolders/
!frontend/editor/src/proprietary/components/watchedFolders/
clientWebUI/
# Scratch dir used by local fixture-regeneration runs (see
# app/proprietary/src/test/resources/db-migration-fixtures/README.md).
@@ -174,7 +174,7 @@ app/proprietary/build
common/build
proprietary/build
stirling-pdf/build
frontend/src-tauri/provisioner/target
frontend/editor/src-tauri/provisioner/target
# Byte-compiled / optimized / DLL files
__pycache__/
@@ -225,7 +225,7 @@ out/
*.asc
# Allow test fixture certificates (synthetic, no real credentials)
!frontend/src/editor/core/tests/test-fixtures/certs/**
!frontend/editor/src/core/tests/test-fixtures/certs/**
# SSH Keys
*.pub
@@ -267,7 +267,7 @@ node_modules/
*compact*.json
test_batch.json
*.backup.*.json
frontend/public/locales/*/translation.backup*.json
frontend/editor/public/locales/*/translation.backup*.json
# Development/build artifacts
.gradle-cache/
@@ -292,4 +292,4 @@ docs/type3/signatures/
*.playwright-mcp.png
# Local screenshot artifacts from *-screenshots.spec.ts
frontend/screenshots/
frontend/editor/screenshots/
+3 -3
View File
@@ -15,14 +15,14 @@ testing/compose/validate-mcp-test.sh:curl-auth-header:92
testing/compose/validate-mcp-test.sh:curl-auth-header:116
# Storybook example showing curl with a fake Bearer token placeholder (sk_live_a3f8...).
frontend/src/editor/proprietary/ui/CodeBlock.stories.tsx:curl-auth-header:5
frontend/editor/src/proprietary/ui/CodeBlock.stories.tsx:curl-auth-header:5
# Truncated placeholder API key in portal docs example (sk_live_8f2c...e10) - not a real secret.
frontend/src/processor/proprietary/components/docs/GettingStartedSection.tsx:generic-api-key:30
frontend/editor/src/portal/components/docs/GettingStartedSection.tsx:generic-api-key:30
# False positive: generic-api-key matches the Java type name "X509Certificate"
# in a method signature (CreateSignatureBase.resolveSignatureAlgorithm) - not a secret.
app/core/src/main/java/org/apache/pdfbox/examples/signature/CreateSignatureBase.java:generic-api-key:224
app/core/src/main/java/stirling/software/SPDF/pdf/signature/CreateSignatureBase.java:generic-api-key:224
# 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.
+1 -1
View File
@@ -1,5 +1,5 @@
{
"ignoredFiles": [
"frontend/src-tauri/icons/icon.png"
"frontend/editor/src-tauri/icons/icon.png"
]
}
+16 -6
View File
@@ -39,6 +39,7 @@ tasks:
provisioner:
desc: "Build installer provisioner"
platforms: [windows]
dir: editor
cmds:
- node scripts/build-provisioner.mjs
@@ -46,48 +47,55 @@ tasks:
desc: "Start Tauri desktop dev mode"
deps: [prepare]
ignore_error: true
dir: editor
cmds:
- npx tauri dev --no-watch
build:
desc: "Build Tauri desktop app (production)"
deps: [prepare]
dir: editor
cmds:
- npx tauri build
build:dev:
desc: "Build Tauri desktop app (dev, no bundling)"
deps: [prepare]
dir: editor
cmds:
- npx tauri build --no-bundle
build:dev:mac:
desc: "Build Tauri desktop .app bundle (macOS)"
deps: [prepare]
dir: editor
cmds:
- npx tauri build --bundles app --config '{"bundle":{"createUpdaterArtifacts":false}}'
build:dev:windows:
desc: "Build Tauri desktop NSIS installer (Windows)"
deps: [prepare]
dir: editor
cmds:
- npx tauri build --bundles nsis --config '{"bundle":{"createUpdaterArtifacts":false}}'
build:dev:linux:
desc: "Build Tauri desktop AppImage (Linux)"
deps: [prepare]
dir: editor
cmds:
- npx tauri build --bundles appimage --config '{"bundle":{"createUpdaterArtifacts":false}}'
test:
desc: "Run Tauri/Cargo tests"
deps: [prepare]
dir: src-tauri
dir: editor/src-tauri
cmds:
- cargo test
clean:
desc: "Clean Tauri/Cargo build artifacts"
dir: editor
cmds:
- task: jlink:clean
- cd src-tauri && cargo clean
@@ -109,6 +117,7 @@ tasks:
jlink:verify:
desc: "Fail the build if the bundled JRE is older than the app JAR requires"
dir: editor
env:
REQUIRED_JAVA: "{{.REQUIRED_JAVA}}"
cmds:
@@ -126,15 +135,15 @@ tasks:
platforms: [windows]
- cmd: ./gradlew bootJar --no-daemon -PjpdfiumPlatforms={{.JPDFIUM_PLATFORMS}}
platforms: [linux, darwin]
- mkdir -p frontend/src-tauri/libs
- cp app/core/build/libs/stirling-pdf-*.jar frontend/src-tauri/libs/
- mkdir -p frontend/editor/src-tauri/libs
- cp app/core/build/libs/stirling-pdf-*.jar frontend/editor/src-tauri/libs/
status:
- test -f frontend/src-tauri/libs/stirling-pdf-*.jar
- test -f frontend/editor/src-tauri/libs/stirling-pdf-*.jar
jlink:runtime:
desc: "Create custom JRE with jlink"
deps: [jlink:jar]
dir: src-tauri
dir: editor/src-tauri
cmds:
- rm -rf runtime/jre
- mkdir -p runtime
@@ -193,7 +202,7 @@ tasks:
jlink:clean:
desc: "Remove JLink runtime and bundled JARs"
dir: src-tauri
dir: editor/src-tauri
cmds:
- rm -rf libs runtime
@@ -206,6 +215,7 @@ tasks:
desc: "Create universal (arm64+x86_64) JRE for the macOS Tauri build"
deps: [jlink:jar]
platforms: [darwin]
dir: editor
env:
JLINK_MODULES: "{{.JLINK_MODULES}}"
OUTPUT_DIR: src-tauri/runtime/jre
+14 -5
View File
@@ -3,18 +3,27 @@ version: '3'
tasks:
install:
desc: "Install Playwright browsers"
dir: frontend
dir: frontend/editor
deps: [ ':frontend:install' ]
cmds:
- npx playwright install {{.CLI_ARGS}} --with-deps
stubbed:
desc: "Run stubbed E2E tests"
dir: frontend
dir: frontend/editor
deps: [ ':frontend:prepare' ]
cmds:
- npx playwright test --project=stubbed {{.CLI_ARGS}}
stubbed-project:
desc: "Run the stubbed E2E suite for a single Playwright project"
dir: frontend/editor
deps: [ ':frontend:prepare' ]
vars:
PROJECT: '{{.PROJECT | default "stubbed"}}'
cmds:
- npx playwright test --project={{.PROJECT}} {{.CLI_ARGS}}
live:
desc: "Run live E2E tests"
summary: |
@@ -82,7 +91,7 @@ tasks:
live:runner:
internal: true
deps: [ ':frontend:prepare' ]
dir: frontend
dir: frontend/editor
vars:
BASE_DIR: '{{.ROOT_DIR}}/.test-state/playwright'
cmds:
@@ -151,14 +160,14 @@ tasks:
up first with:
task e2e:oauth:up
task e2e:saml:up
dir: frontend
dir: frontend/editor
deps: [ ':frontend:prepare' ]
cmds:
- npx playwright test --project=enterprise {{.CLI_ARGS}}
cross-browser:
desc: "Run stubbed E2E tests on Chromium, Firefox, and WebKit"
dir: frontend
dir: frontend/editor
deps: [ ':frontend:prepare' ]
cmds:
- npx playwright test --project=stubbed --project=stubbed-firefox --project=stubbed-webkit {{.CLI_ARGS}}
+54 -13
View File
@@ -2,22 +2,49 @@ version: '3'
tasks:
install:
desc: "Install engine dependencies"
desc: "Install engine runtime and development dependencies"
run: once
cmds:
- uv python install 3.13.8
- uv sync
- uv sync --locked --group engine --group engine-dev
sources:
- uv.lock
- pyproject.toml
status:
- test -d .venv
lock:
desc: "Update the engine lockfile from project metadata"
cmds:
- uv lock
lock:upgrade:
desc: "Upgrade allowed engine dependencies and update the lockfile"
cmds:
- uv lock --upgrade
lock:check:
desc: "Check whether the engine lockfile is current"
cmds:
- uv lock --check
update:
desc: "Upgrade engine dependencies and synchronize the environment"
cmds:
- task: lock:upgrade
- uv sync --locked --group engine --group engine-dev
update:all:
desc: "Upgrade all Python dependency groups and synchronize the environment"
cmds:
- task: lock:upgrade
- uv sync --locked --all-groups
prepare:
desc: "Set up engine .env from template"
deps: [install]
cmds:
- uv run scripts/setup_env.py
- uv run --locked --group engine --group engine-dev scripts/setup_env.py
sources:
- scripts/setup_env.py
generates:
@@ -33,7 +60,7 @@ tasks:
env:
PYTHONUNBUFFERED: "1"
cmds:
- uv run uvicorn stirling.api.app:app --host 0.0.0.0 --port {{.PORT}} --workers "${STIRLING_ENGINE_WORKERS:-4}"
- uv run --locked --group engine uvicorn stirling.api.app:app --host 0.0.0.0 --port {{.PORT}} --workers "${STIRLING_ENGINE_WORKERS:-4}"
dev:
desc: "Start engine dev server with hot reload"
@@ -45,43 +72,57 @@ tasks:
env:
PYTHONUNBUFFERED: "1"
cmds:
- uv run uvicorn stirling.api.app:app --host 0.0.0.0 --port {{.PORT}} --reload
- uv run --locked --group engine --group engine-dev uvicorn stirling.api.app:app --host 0.0.0.0 --port {{.PORT}} --reload
lint:
desc: "Run linting"
deps: [install]
cmds:
- uv run ruff check .
- uv run --locked --group engine --group engine-dev ruff check .
lint:fix:
desc: "Auto-fix lint issues"
deps: [install]
cmds:
- uv run ruff check . --fix
- uv run --locked --group engine --group engine-dev ruff check . --fix
format:
desc: "Auto-fix code formatting"
deps: [install]
cmds:
- uv run ruff format .
- uv run --locked --group engine --group engine-dev ruff format .
format:check:
desc: "Check code formatting"
deps: [install]
cmds:
- uv run ruff format . --diff
- uv run --locked --group engine --group engine-dev ruff format . --diff
typecheck:
desc: "Run type checking"
deps: [install]
cmds:
- uv run pyright . --warnings
- uv run --locked --group engine --group engine-dev pyright . --warnings
test:
desc: "Run tests"
deps: [prepare]
cmds:
- uv run pytest tests
- uv run --locked --group engine --group engine-dev pytest tests
test:coverage:
desc: "Run tests with coverage reporting"
deps: [prepare]
cmds:
- >-
uv run pytest tests
--cov=src/stirling
--cov-fail-under=90
--cov-report=term-missing
--cov-report=xml:coverage/coverage.xml
--cov-report=html:coverage/html
--cov-report=json:coverage/coverage.json
- uv run python scripts/check_coverage.py coverage/coverage.json --minimum 90
fix:
desc: "Auto-fix lint + format"
@@ -102,7 +143,7 @@ tasks:
desc: "Generate tool_models.py from Java OpenAPI spec (SwaggerDoc.json)"
deps: [install, ":backend:swagger"]
cmds:
- uv run python scripts/generate_tool_models.py --spec ../SwaggerDoc.json --output src/stirling/models/tool_models.py --io-output src/stirling/models/tool_io.py
- uv run --locked --group engine --group engine-dev python scripts/generate_tool_models.py --spec ../SwaggerDoc.json --output src/stirling/models/tool_models.py --io-output src/stirling/models/tool_io.py
sources:
- ../SwaggerDoc.json
- scripts/generate_tool_models.py
@@ -114,7 +155,7 @@ tasks:
desc: "Fail if the committed tool models are out of date"
deps: [install, ":backend:swagger"]
cmds:
- uv run python scripts/generate_tool_models.py --spec ../SwaggerDoc.json --output src/stirling/models/tool_models.py --io-output src/stirling/models/tool_io.py --check
- uv run --locked --group engine --group engine-dev python scripts/generate_tool_models.py --spec ../SwaggerDoc.json --output src/stirling/models/tool_models.py --io-output src/stirling/models/tool_io.py --check
clean:
desc: "Clean build artifacts"
+69 -78
View File
@@ -3,7 +3,7 @@ version: '3'
# Tasks operate from the workspace root (frontend/). Editor commands pass
# `editor` as the vite project root (positional after `build` / before the
# mode flag) or use `--project editor/...` for tsc — so the editor lives
# under frontend/ without each task needing a cd.
# under frontend/editor/ without each task needing a cd.
tasks:
install:
@@ -26,34 +26,34 @@ tasks:
vars:
MODE: '{{.MODE | default ""}}'
cmds:
- npx tsx scripts/setup-env.mts{{if .MODE}} --{{.MODE}}{{end}}
- npx tsx editor/scripts/setup-env.mts{{if .MODE}} --{{.MODE}}{{end}}
sources:
- scripts/setup-env.mts
- editor/scripts/setup-env.mts
generates:
- .env.local
- .env{{if .MODE}}.{{.MODE}}{{end}}.local
- editor/.env.local
- editor/.env{{if .MODE}}.{{.MODE}}{{end}}.local
prepare:icons:
internal: true
run: once
deps: [install]
cmds:
- node scripts/generate-icons.js
- node editor/scripts/generate-icons.js
prepare:og:
internal: true
run: when_changed
desc: "Regenerate OG/social-preview metadata from the tool registry"
cmds:
- node scripts/generate-og-metadata.mjs
- node editor/scripts/generate-og-metadata.mjs
sources:
- src/editor/core/types/toolId.ts
- src/editor/core/utils/urlMapping.ts
- src/editor/core/data/useTranslatedToolRegistry.tsx
- public/og_images/*.png
- editor/src/core/types/toolId.ts
- editor/src/core/utils/urlMapping.ts
- editor/src/core/data/useTranslatedToolRegistry.tsx
- editor/public/og_images/*.png
generates:
- src/editor/core/data/ogImageMap.json
- public/og-metadata.json
- editor/src/core/data/ogImageMap.json
- editor/public/og-metadata.json
prepare:
desc: "Set up dev environment"
@@ -88,7 +88,7 @@ tasks:
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}}
cmds:
- npx vite --mode {{.MODE}} --port {{.PORT}}{{if .OPEN}} --open{{end}}
- npx vite editor --mode {{.MODE}} --port {{.PORT}}{{if .OPEN}} --open{{end}}
dev:
desc: "Start frontend dev server"
@@ -143,13 +143,13 @@ tasks:
desc: "Production build (default mode)"
deps: [prepare]
cmds:
- npx vite build
- npx vite build editor
build:core:
desc: "Build for core mode"
deps: [prepare]
cmds:
- npx vite build --mode core
- npx vite build editor --mode core
build:proprietary:
desc: "Build for proprietary mode"
@@ -157,7 +157,7 @@ tasks:
vars:
PREVIEW: '{{.PREVIEW | default ""}}'
cmds:
- '{{if .PREVIEW}}VITE_BUILD_FOR_PREVIEW=1 {{end}}npx vite build --mode proprietary'
- '{{if .PREVIEW}}VITE_BUILD_FOR_PREVIEW=1 {{end}}npx vite build editor --mode proprietary'
build:saas:
desc: "Build for SaaS mode"
@@ -165,7 +165,7 @@ tasks:
- task: prepare
vars: { MODE: saas }
cmds:
- npx vite build --mode saas
- npx vite build editor --mode saas
build:desktop:
desc: "Build for desktop mode"
@@ -173,13 +173,13 @@ tasks:
- task: prepare
vars: { MODE: desktop }
cmds:
- npx vite build --mode desktop
- npx vite build editor --mode desktop
build:prototypes:
desc: "Build for prototypes mode"
deps: [prepare]
cmds:
- npx vite build --mode prototypes
- npx vite build editor --mode prototypes
storybook:
@@ -211,11 +211,13 @@ tasks:
- npx vitest run --config .storybook/vitest.config.ts {{.CLI_ARGS}}
storybook:a11y:
desc: "a11y regression gate over every story: fail only on NEW axe violations"
desc: "a11y regression gate over every story, light and dark: fail only on NEW axe violations"
deps: [prepare, storybook:browser]
cmds:
- node .storybook/a11y-scan.mjs
- node .storybook/a11y-check.mjs --in .a11y-scan --manifest .a11y-scan/manifest.txt
- SCAN_THEME=dark node .storybook/a11y-scan.mjs
- node .storybook/a11y-check.mjs --in .a11y-scan --manifest .a11y-scan/manifest.txt --baseline .storybook/a11y-baseline.dark.json
storybook:a11y:changed:
desc: "a11y gate over the stories this branch affects (default base origin/main)"
@@ -244,13 +246,17 @@ tasks:
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
storybook:a11y:record:
desc: "Re-record the a11y baseline (run after intentionally fixing/adding violations)"
desc: "Re-record both a11y baselines (run after intentionally fixing/adding violations)"
deps: [prepare, storybook:browser]
cmds:
- node .storybook/a11y-scan.mjs
- node .storybook/a11y-check.mjs --in .a11y-scan --manifest .a11y-scan/manifest.txt --record
- SCAN_THEME=dark node .storybook/a11y-scan.mjs
- node .storybook/a11y-check.mjs --in .a11y-scan --manifest .a11y-scan/manifest.txt --record --baseline .storybook/a11y-baseline.dark.json
# ============================================================
# Code quality
@@ -260,8 +266,7 @@ tasks:
desc: "Run linting"
deps: [install]
cmds:
- task: lint:eslint
- task: lint:dpdm
- task: lint:oxlint
- task: lint:colors
- task: lint:css
@@ -272,43 +277,35 @@ tasks:
# Covers the whole editor tree, including the portal/processor layer and
# public/css. Vendored CSS and build output are excluded via ignoreFiles
# in stylelint.config.mjs.
- npx stylelint "src/**/*.css"
- npx stylelint "editor/**/*.css"
lint:colors:
desc: "Enforce theme tokens — no hardcoded colours or raw primitives in components"
aliases: [lint:colours]
deps: [install]
cmds:
- node scripts/lint/theme-lint.mjs
- node scripts/lint/theme-lint.mjs css-colors
- node scripts/lint/theme-lint.mjs code-colors
- node scripts/lint/theme-lint.mjs no-primitives
- node editor/scripts/lint/theme-lint.mjs
- node editor/scripts/lint/theme-lint.mjs css-colors
- node editor/scripts/lint/theme-lint.mjs code-colors
- node editor/scripts/lint/theme-lint.mjs no-primitives
contrast:
desc: "Report low-contrast theme token pairs (warning only, never blocks)"
deps: [install]
cmds:
- node scripts/lint/theme-lint.mjs contrast
- node editor/scripts/lint/theme-lint.mjs contrast
lint:eslint:
desc: "Run ESLint linting"
lint:oxlint:
desc: "Run oxlint linting"
deps: [install]
cmds:
- npx eslint --max-warnings=0
lint:dpdm:
desc: "Run circular import linting"
deps: [install]
cmds:
# Globs so dpdm walks the whole tree. dpdm expands the braces itself, so this is
# shell-agnostic. Covers the whole editor tree, including the portal layer.
- npx dpdm "src/**/*.{ts,tsx}" --circular --no-warning --no-tree --exit-code circular:1
- npx oxlint --config oxlint.config.ts --max-warnings=0
lint:fix:
desc: "Auto-fix lint issues"
deps: [install]
cmds:
- npx eslint --fix
- npx oxlint --config oxlint.config.ts --fix
format:
desc: "Auto-fix code formatting"
@@ -343,14 +340,14 @@ tasks:
deps: [prepare]
cmds:
- task: typecheck:_run
vars: { PROJECT: src/editor/core/tsconfig.json }
vars: { PROJECT: editor/src/core/tsconfig.json }
typecheck:proprietary:
desc: "Typecheck proprietary build variant"
deps: [prepare]
cmds:
- task: typecheck:_run
vars: { PROJECT: src/editor/proprietary/tsconfig.json }
vars: { PROJECT: editor/src/proprietary/tsconfig.json }
typecheck:saas:
desc: "Typecheck SaaS build variant"
@@ -359,7 +356,7 @@ tasks:
vars: { MODE: saas }
cmds:
- task: typecheck:_run
vars: { PROJECT: src/editor/saas/tsconfig.json }
vars: { PROJECT: editor/src/saas/tsconfig.json }
typecheck:desktop:
desc: "Typecheck desktop build variant"
@@ -368,35 +365,35 @@ tasks:
vars: { MODE: desktop }
cmds:
- task: typecheck:_run
vars: { PROJECT: src/editor/desktop/tsconfig.json }
vars: { PROJECT: editor/src/desktop/tsconfig.json }
typecheck:cloud:
desc: "Typecheck cloud shared layer (standalone)"
deps: [prepare]
cmds:
- task: typecheck:_run
vars: { PROJECT: src/editor/cloud/tsconfig.json }
vars: { PROJECT: editor/src/cloud/tsconfig.json }
typecheck:scripts:
desc: "Typecheck scripts"
deps: [prepare]
cmds:
- task: typecheck:_run
vars: { PROJECT: scripts/tsconfig.json }
vars: { PROJECT: editor/scripts/tsconfig.json }
typecheck:prototypes:
desc: "Typecheck prototypes build variant"
deps: [prepare]
cmds:
- task: typecheck:_run
vars: { PROJECT: src/editor/prototypes/tsconfig.json }
vars: { PROJECT: editor/src/prototypes/tsconfig.json }
typecheck:portal:
desc: "Typecheck developer portal build variant"
deps: [install]
cmds:
- task: typecheck:_run
vars: { PROJECT: src/processor/proprietary/tsconfig.json }
vars: { PROJECT: editor/src/portal/tsconfig.json }
typecheck:storybook:
desc: "Typecheck Storybook config and stories"
@@ -433,7 +430,7 @@ tasks:
og:check:
desc: "Fail if committed OG/social-preview metadata is out of date"
cmds:
- node scripts/generate-og-metadata.mjs --check
- node editor/scripts/generate-og-metadata.mjs --check
check:all:
desc: "Full CI quality gate"
@@ -460,35 +457,29 @@ tasks:
test:editor:
desc: "Run editor tests"
deps: [prepare]
vars:
COVERAGE: '{{.COVERAGE | default .CI | default "false"}}'
cmds:
- npx vitest run --root .
- >
npx vitest run --root editor
{{if eq .COVERAGE "true"}}--coverage
--coverage.provider=v8
--coverage.reporter=text-summary
--coverage.reporter=json-summary
--coverage.reporter=html
--coverage.reportsDirectory=./coverage{{end}}
test:watch:
desc: "Run tests in watch mode"
deps: [prepare]
cmds:
- npx vitest --watch --root .
- npx vitest --watch --root editor
test:coverage:
desc: "Run tests with coverage (one-shot; CI-friendly)."
deps: [prepare]
cmds:
# `vitest run` makes this CI-safe (the bare `vitest` form enters watch
# mode). Explicit reporter list because v8 + json-summary is what the
# coverage-summary.py helper consumes; html/text are kept for humans.
#
# reportsDirectory is pinned to ./coverage relative to vitest's root
# (--root .), so output lands at frontend/coverage/. The
# CI upload step reads from that path. An earlier attempt with
# `./editor/coverage` double-nested into frontend/coverage;
# pinning future-proofs against vitest changing the default.
- >
npx vitest run --root . --coverage
--coverage.provider=v8
--coverage.reporter=text-summary
--coverage.reporter=json-summary
--coverage.reporter=html
--coverage.reportsDirectory=./coverage
- task: test:editor
vars: { COVERAGE: "true" }
# ============================================================
# Code Generation
@@ -498,25 +489,25 @@ tasks:
desc: "Generate tool API types from the Java OpenAPI spec"
deps: [install, ":backend:swagger"]
cmds:
- npx tsx scripts/generate-tool-api-types.mts --spec ../SwaggerDoc.json --output src/editor/core/types/toolApiTypes.ts --io-output src/editor/core/types/toolIO.ts
- npx tsx editor/scripts/generate-tool-api-types.mts --spec ../SwaggerDoc.json --output editor/src/core/types/toolApiTypes.ts --io-output editor/src/core/types/toolIO.ts
sources:
- scripts/generate-tool-api-types.mts
- editor/scripts/generate-tool-api-types.mts
- ../SwaggerDoc.json
generates:
- src/editor/core/types/toolApiTypes.ts
- src/editor/core/types/toolIO.ts
- editor/src/core/types/toolApiTypes.ts
- editor/src/core/types/toolIO.ts
tool-models:check:
desc: "Fail if committed tool API types are out of date"
deps: [install, ":backend:swagger"]
cmds:
- npx tsx scripts/generate-tool-api-types.mts --spec ../SwaggerDoc.json --output src/editor/core/types/toolApiTypes.ts --io-output src/editor/core/types/toolIO.ts --check
- npx tsx editor/scripts/generate-tool-api-types.mts --spec ../SwaggerDoc.json --output editor/src/core/types/toolApiTypes.ts --io-output editor/src/core/types/toolIO.ts --check
licenses:generate:
desc: "Generate frontend license report"
deps: [install]
cmds:
- node scripts/generate-licenses.js
- node editor/scripts/generate-licenses.js
# ============================================================
# Clean
@@ -525,7 +516,7 @@ tasks:
clean:
desc: "Clean build artifacts and caches"
cmds:
- cmd: powershell rm -Recurse -Force -ErrorAction SilentlyContinue node_modules/.vite, dist, dist
- cmd: powershell rm -Recurse -Force -ErrorAction SilentlyContinue node_modules/.vite, editor/dist, dist
platforms: [windows]
- cmd: rm -rf node_modules/.vite dist dist
- cmd: rm -rf node_modules/.vite editor/dist dist
platforms: [linux, darwin]
+21 -16
View File
@@ -7,10 +7,11 @@ vars:
# File selections as git pathspecs: git does the include/exclude matching, so
# there is no grep/xargs and it behaves identically on every platform.
PY_FILES: >-
'scripts/*.py'
'scripts/**/*.py'
'.github/scripts/*.py'
'app/core/src/main/resources/static/python/*.py'
':(exclude)*split_photos.py'
'testing/**/*.py'
SPELL_FILES: >-
'*.html'
'*.css'
@@ -21,7 +22,7 @@ vars:
':(exclude).devcontainer/*'
':(exclude)app/core/src/main/resources/*'
':(exclude)app/proprietary/src/main/resources/*'
':(exclude)frontend/public/vendor/*'
':(exclude)frontend/editor/public/vendor/*'
':(exclude)*Dockerfile*'
':(exclude)*pdfjs*'
':(exclude)*thirdParty*'
@@ -39,12 +40,16 @@ vars:
':(exclude)*.min.*'
':(exclude)*diff.js'
':(exclude).github/workflows/*'
LOCALE_TOML: 'frontend/public/locales/*/translation.toml'
LOCALE_TOML: 'frontend/editor/public/locales/*/translation.toml'
# gitleaks is pinned + checksum-verified by scripts/pre-commit/install_gitleaks.py,
# which owns the version and caches the binary here.
GITLEAKS_BIN: '.task/bin/gitleaks{{if eq OS "windows"}}.exe{{end}}'
env:
# Keep repository-wide checks isolated from the engine runtime environment.
UV_PROJECT_ENVIRONMENT: '.venv-pre-commit'
tasks:
default:
desc: "Check formatting, spelling, and secrets across the repo"
@@ -76,19 +81,19 @@ tasks:
desc: "Install the pinned pre-commit Python tools"
run: once
cmds:
- uv sync --project scripts/pre-commit --locked
- uv sync --project engine --locked --group pre-commit
sources:
- scripts/pre-commit/uv.lock
- scripts/pre-commit/pyproject.toml
- engine/uv.lock
- engine/pyproject.toml
status:
- test -d scripts/pre-commit/.venv
- test -d engine/.venv-pre-commit
clean:
desc: "Remove the cached gitleaks binary and the tool virtualenv"
desc: "Remove the cached gitleaks binary and the pre-commit virtualenv"
cmds:
- cmd: rm -rf scripts/pre-commit/.venv .task/bin/gitleaks
- cmd: rm -rf engine/.venv-pre-commit .task/bin/gitleaks
platforms: [linux, darwin]
- cmd: cmd /c "rmdir /s /q scripts\pre-commit\.venv & del /q .task\bin\gitleaks.exe"
- cmd: cmd /c "rmdir /s /q engine\.venv-pre-commit & del /q .task\bin\gitleaks.exe"
platforms: [windows]
ignore_error: true
@@ -97,26 +102,26 @@ tasks:
ruff:
deps: [install]
cmds:
- uv run --project scripts/pre-commit --no-sync ruff check --line-length=127 {{if .FIX}}--fix {{end}}$(git ls-files {{.PY_FILES}})
- uv run --project engine --locked --group pre-commit ruff check --config engine/pyproject.toml --line-length=127 {{if .FIX}}--fix {{end}}$(git ls-files {{.PY_FILES}})
ruff-format:
deps: [install]
cmds:
- uv run --project scripts/pre-commit --no-sync ruff format {{if .FIX}}{{else}}--check {{end}}$(git ls-files {{.PY_FILES}})
- uv run --project engine --locked --group pre-commit ruff format --config engine/pyproject.toml --line-length=127 {{if .FIX}}{{else}}--check {{end}}$(git ls-files {{.PY_FILES}})
codespell:
deps: [install]
cmds:
- uv run --project scripts/pre-commit --no-sync codespell --ignore-words-list=thirdParty,tabEl,tabEls,Sie,ist,fulfilment --quiet-level=2 $(git ls-files {{.SPELL_FILES}})
- uv run --project engine --locked --group pre-commit codespell --ignore-words-list=thirdParty,tabEl,tabEls,Sie,ist,fulfilment --quiet-level=2 $(git ls-files {{.SPELL_FILES}})
toml-sort:
deps: [install]
cmds:
- uv run --project scripts/pre-commit --no-sync python scripts/pre-commit/sort_locale_toml.py {{if .FIX}}--fix {{end}}{{.LOCALE_TOML}}
- uv run --project engine --locked --group pre-commit python scripts/pre-commit/sort_locale_toml.py {{if .FIX}}--fix {{end}}{{.LOCALE_TOML}}
whitespace:
cmds:
- uv run --no-project python scripts/pre-commit/whitespace.py {{if .FIX}}--fix {{end}}{{.WS_FILES}}
- uv run --project engine --locked --group pre-commit python scripts/pre-commit/whitespace.py {{if .FIX}}--fix {{end}}{{.WS_FILES}}
gitleaks:
deps: [gitleaks-bin]
@@ -130,4 +135,4 @@ tasks:
internal: true
desc: "Ensure the pinned, checksum-verified gitleaks binary is cached in .task/bin"
cmds:
- uv run --no-project python scripts/pre-commit/install_gitleaks.py
- uv run --project engine --locked --group pre-commit python scripts/pre-commit/install_gitleaks.py
+2 -3
View File
@@ -2,8 +2,6 @@
"recommendations": [
"elagil.pre-commit-helper", // Support for pre-commit hooks to enforce code quality
"josevseb.google-java-format-for-vs-code", // Google Java code formatter to follow the Google Java Style Guide
"ms-python.black-formatter", // Python code formatter using Black
"ms-python.flake8", // Flake8 linter for Python to enforce code quality
"ms-python.python", // Official Microsoft Python extension with IntelliSense, debugging, and Jupyter support
"ms-vscode-remote.vscode-remote-extensionpack", // Remote Development Pack for SSH, WSL, and Containers
// "Oracle.oracle-java", // Oracle Java extension with additional features for Java development
@@ -19,6 +17,7 @@
"yzhang.markdown-all-in-one", // Markdown All-in-One extension for enhanced Markdown editing
"stylelint.vscode-stylelint", // Stylelint extension for CSS and SCSS linting
"redhat.vscode-yaml", // YAML extension for Visual Studio Code
"dbaeumer.vscode-eslint", // ESLint extension for TypeScript linting
"tamasfe.even-better-toml", // TOML language support and formatting
"oxc.oxc-vscode", // Oxc (oxlint) extension for JavaScript/TypeScript linting
]
}
+2 -1
View File
@@ -20,8 +20,9 @@
"editor.defaultFormatter": "vscode.json-language-features"
},
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter"
"editor.defaultFormatter": "charliermarsh.ruff"
},
"ruff.configuration": "${workspaceFolder}/engine/pyproject.toml",
"[gradle-kotlin-dsl]": {
"editor.defaultFormatter": "vscjava.vscode-gradle"
},
+6 -6
View File
@@ -10,14 +10,14 @@ When adding tools, follow this systematic approach using the established pattern
Create these files in the correct directories:
```
frontend/src/hooks/tools/[toolName]/
frontend/editor/src/hooks/tools/[toolName]/
├── use[ToolName]Parameters.ts # Parameter definitions and validation
└── use[ToolName]Operation.ts # Tool operation logic using useToolOperation
frontend/src/components/tools/[toolName]/
frontend/editor/src/components/tools/[toolName]/
└── [ToolName]Settings.tsx # Settings UI component (if needed)
frontend/src/tools/
frontend/editor/src/tools/
└── [ToolName].tsx # Main tool component
```
@@ -128,7 +128,7 @@ export default [ToolName] as ToolComponent;
## 3. Register Tool in System
Update these files to register your new tool:
**Tool Registry** (`frontend/src/data/useTranslatedToolRegistry.tsx`):
**Tool Registry** (`frontend/editor/src/data/useTranslatedToolRegistry.tsx`):
1. Add imports at the top:
```typescript
import [ToolName] from "../tools/[ToolName]";
@@ -155,7 +155,7 @@ import [ToolName]Settings from "../components/tools/[toolName]/[ToolName]Setting
## 4. Add Tooltips (Optional but Recommended)
Create user-friendly tooltips to help non-technical users understand your tool. **Use simple, clear language - avoid technical jargon:**
**Tooltip Hook** (`frontend/src/components/tooltips/use[ToolName]Tips.ts`):
**Tooltip Hook** (`frontend/editor/src/components/tooltips/use[ToolName]Tips.ts`):
```typescript
import { useTranslation } from 'react-i18next';
import { TooltipContent } from '../../types/tips';
@@ -202,7 +202,7 @@ const [ToolName] = (props: BaseToolProps) => {
## 5. Add Translations
Update translation files. **Important: Only update `en-US` files** - other languages are handled separately.
**File to update:** `frontend/public/locales/en-US/translation.toml`
**File to update:** `frontend/editor/public/locales/en-US/translation.toml`
**Required Translation Keys**:
```toml
+18 -18
View File
@@ -139,10 +139,10 @@ The project structure is defined in `engine/pyproject.toml`. Any new dependencie
#### Environment Variables
- All `VITE_*` variables must be declared in the appropriate committed env file:
- `frontend/.env` — core and shared vars (base, loaded in every mode)
- `frontend/.env.proprietary` — proprietary-only vars, e.g. the admin portal's SaaS/account-link keys (layered on top of `.env` in proprietary mode)
- `frontend/.env.saas` — SaaS-only vars (layered on top of `.env` in SaaS mode)
- `frontend/.env.desktop` — desktop (Tauri)-only vars (layered on top of `.env` in desktop mode)
- `frontend/editor/.env` — core and shared vars (base, loaded in every mode)
- `frontend/editor/.env.proprietary` — proprietary-only vars, e.g. the admin portal's SaaS/account-link keys (layered on top of `.env` in proprietary mode)
- `frontend/editor/.env.saas` — SaaS-only vars (layered on top of `.env` in SaaS mode)
- `frontend/editor/.env.desktop` — desktop (Tauri)-only vars (layered on top of `.env` in desktop mode)
- These files are committed to Git and must not contain private keys
- Local overrides (API keys, machine-specific settings) go in uncommitted sibling `.env.local` / `.env.saas.local` / `.env.desktop.local` files — Vite automatically layers them on top
- Never use `|| 'hardcoded-fallback'` inline — put defaults in the committed env files
@@ -153,9 +153,9 @@ The project structure is defined in `engine/pyproject.toml`. Any new dependencie
#### Import Paths - CRITICAL
**ALWAYS use `@app/*` for imports.** Do not use `@core/*` or `@proprietary/*` unless explicitly wrapping/extending a lower layer implementation.
For a broader explanation of the frontend layering and override architecture, read @frontend/DeveloperGuide.md
For a broader explanation of the frontend layering and override architecture, read @frontend/editor/DeveloperGuide.md
Before touching colours or theming (tokens, dark mode, accent colours), read @frontend/src/editor/core/theme/README.md — it explains the palette/`--c-*` token system and the rule that literal colours live only in `primitives.css`.
Before touching colours or theming (tokens, dark mode, accent colours), read @frontend/editor/src/core/theme/README.md — it explains the palette/`--c-*` token system and the rule that literal colours live only in `primitives.css`.
```typescript
// ✅ CORRECT - Use @app/* for all imports
@@ -192,7 +192,7 @@ What goes where:
- **saas** — web-only: Supabase web auth, AuthCallback, avatar canvas, `window.location`.
- **desktop** — Tauri-only: keyring authService, tauriHttpClient, native files/windows, backend routing.
`cloud/` MUST NOT import `@supabase/*`, `@tauri-apps/*`, raw `fetch`, `window.location`, `localStorage`, `sessionStorage`, or `import.meta.env.VITE_*` (enforced by ESLint). It reaches platform-specific things only via `@app/*` seams: `services/apiClient`, `auth/session.getAccessToken`, `auth/supabase`, `platform/openExternal`, `services/billing`, `hooks/useSaaSMode` — each provided per-platform in `saas/` and `desktop/`.
`cloud/` MUST NOT import `@supabase/*`, `@tauri-apps/*`, raw `fetch`, `window.location`, `localStorage`, `sessionStorage`, or `import.meta.env.VITE_*` (all enforced by the linter). It reaches platform-specific things only via `@app/*` seams: `services/apiClient`, `auth/session.getAccessToken`, `auth/supabase`, `platform/openExternal`, `services/billing`, `hooks/useSaaSMode` — each provided per-platform in `saas/` and `desktop/`.
Rule of thumb — **move, don't copy**: share via `cloud/`, override by shadowing the same `@app/*` path in a leaf (`saas/` or `desktop/`).
@@ -270,7 +270,7 @@ Frontend designed for **stateful document processing**:
- No file reloading between tools - performance critical for large PDFs (up to 100GB+)
#### FileContext - Central State Management
**Location**: `frontend/src/editor/core/contexts/FileContext.tsx`
**Location**: `frontend/editor/src/core/contexts/FileContext.tsx`
- **Active files**: Currently loaded PDFs and their variants
- **Tool navigation**: Current mode (viewer/pageEditor/fileEditor/toolName)
- **Memory management**: PDF document cleanup, blob URL lifecycle, Web Worker management
@@ -295,7 +295,7 @@ Without cleanup: browser crashes with memory leaks.
**Architecture**: Modular hook-based system with clear separation of concerns:
- **useToolOperation** (`frontend/src/editor/core/hooks/tools/shared/useToolOperation.ts`): Main orchestrator hook
- **useToolOperation** (`frontend/editor/src/core/hooks/tools/shared/useToolOperation.ts`): Main orchestrator hook
- Coordinates all tool operations with consistent interface
- Integrates with FileContext for operation tracking
- Handles validation, error handling, and UI state management
@@ -383,7 +383,7 @@ return useToolOperation({
### Frontend Directory Structure
The frontend is organized with a clear separation of concerns:
- **`frontend/src/editor/core/`**: Main application code (shared, production-ready components)
- **`frontend/editor/src/core/`**: Main application code (shared, production-ready components)
- **`core/components/`**: React components organized by feature
- `core/components/tools/`: Individual PDF tool implementations
- `core/components/viewer/`: PDF viewer components
@@ -401,17 +401,17 @@ The frontend is organized with a clear separation of concerns:
- **`core/data/`**: Static data (tool taxonomy, etc.)
- **`core/services/`**: Business logic services (PDF processing, storage, etc.)
- **`frontend/src/editor/desktop/`**: Desktop-specific (Tauri) code
- **`frontend/src/editor/proprietary/`**: Proprietary/licensed features
- **`frontend/src-tauri/`**: Tauri (Rust) native desktop application code
- **`frontend/public/`**: Static assets served directly
- **`frontend/editor/src/desktop/`**: Desktop-specific (Tauri) code
- **`frontend/editor/src/proprietary/`**: Proprietary/licensed features
- **`frontend/editor/src-tauri/`**: Tauri (Rust) native desktop application code
- **`frontend/editor/public/`**: Static assets served directly
- `public/locales/`: Translation JSON files
### Component Architecture
- **Static Assets**: CSS, JS, and resources in `src/main/resources/static/` (legacy) + `frontend/public/` (modern)
- **Static Assets**: CSS, JS, and resources in `src/main/resources/static/` (legacy) + `frontend/editor/public/` (modern)
- **Internationalization**:
- Backend: `messages_*.properties` files
- Frontend: JSON files in `frontend/public/locales/` (converted from .properties)
- Frontend: JSON files in `frontend/editor/public/locales/` (converted from .properties)
- Conversion Script: `scripts/convert_properties_to_json.py`
### Configuration Modes
@@ -436,7 +436,7 @@ The frontend is organized with a clear separation of concerns:
4. **Code Style**: Spotless enforces Google Java Format automatically (`task backend:format`)
5. **Translations**:
- Backend: Use helper scripts in `/scripts` for multi-language updates
- Frontend: Update JSON files in `frontend/public/locales/` or use conversion script
- Frontend: Update JSON files in `frontend/editor/public/locales/` or use conversion script
6. **Documentation**: API docs auto-generated and available at `/swagger-ui/index.html`
## Frontend Architecture Status
@@ -454,7 +454,7 @@ The frontend is organized with a clear separation of concerns:
## Translation Rules
- **CRITICAL**: Always update translations in `en-US` only - all other languages (including `en-GB`) are handled separately
- Translation files are located in `frontend/public/locales/`
- Translation files are located in `frontend/editor/public/locales/`
- After changing any translation file, run `task pre-commit:fix`
## Important Notes
+2 -2
View File
@@ -158,7 +158,7 @@ Stirling-PDF/
│ │ │ └── locales/ # Internationalization files (JSON)
│ │ └── vite.config.ts # Vite configuration
│ ├── package.json # Shared workspace dependencies
│ └── eslint.config.mjs # Shared lint config
│ └── oxlint.config.ts # Shared lint config
├── customFiles/ # Custom static files and templates (generated at runtime used to replace existing files)
├── docs/ # Documentation files
├── exampleYmlFiles/ # Example YAML configuration files
@@ -458,7 +458,7 @@ For Stirling 2.0, new features are built as React components:
1. **Create the React Component:**
```typescript
// frontend/src/tools/NewTool.tsx
// frontend/editor/src/tools/NewTool.tsx
import { useState } from 'react';
import { Button, FileInput, Container } from '@mantine/core';
+14 -12
View File
@@ -10,18 +10,20 @@ if that directory exists, is licensed under the license defined in "app/propriet
if that directory exists, is licensed under the license defined in "app/saas/LICENSE".
* All content that resides under the "engine/" directory of this repository,
if that directory exists, is licensed under the license defined in "engine/LICENSE".
* All content that resides under the "frontend/src/editor/proprietary/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/src/editor/proprietary/LICENSE".
* All content that resides under the "frontend/src/editor/desktop/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/src/editor/desktop/LICENSE".
* All content that resides under the "frontend/src/editor/saas/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/src/editor/saas/LICENSE".
* All content that resides under the "frontend/src/editor/cloud/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/src/editor/cloud/LICENSE".
* All content that resides under the "frontend/src/editor/prototypes/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/src/editor/prototypes/LICENSE".
* All content that resides under the "frontend/src/processor/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/src/processor/LICENSE".
* All content that resides under the "frontend/editor/src/proprietary/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/editor/src/proprietary/LICENSE".
* All content that resides under the "frontend/editor/src/desktop/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/editor/src/desktop/LICENSE".
* All content that resides under the "frontend/editor/src/saas/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/editor/src/saas/LICENSE".
* All content that resides under the "frontend/editor/src/cloud/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/editor/src/cloud/LICENSE".
* All content that resides under the "frontend/editor/src/prototypes/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/editor/src/prototypes/LICENSE".
* All content that resides under the "frontend/editor/src/portal/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/editor/src/portal/LICENSE".
* All content that resides under the "frontend/editor/src/portal-saas/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/editor/src/portal-saas/LICENSE".
* Content outside of the above mentioned directories or restrictions above is
available under the MIT License as defined below.
+1 -1
View File
@@ -86,7 +86,7 @@ If you're using Tauri's built-in updater feature:
## Configuration Files
### 1. Tauri Configuration (frontend/src-tauri/tauri.conf.json)
### 1. Tauri Configuration (frontend/editor/src-tauri/tauri.conf.json)
The Windows signing configuration is already set up:
+3 -3
View File
@@ -10,7 +10,7 @@ dependencies {
api 'com.fathzer:javaluator:3.0.6'
api 'com.posthog.java:posthog:1.2.0'
api "org.apache.commons:commons-lang3:${commonsLang3}"
api 'com.drewnoakes:metadata-extractor:2.20.0' // Image metadata extractor
api 'com.drewnoakes:metadata-extractor:2.21.0' // Image metadata extractor
api 'com.vladsch.flexmark:flexmark-html2md-converter:0.64.8'
api "org.apache.pdfbox:pdfbox:$pdfboxVersion"
api "org.apache.pdfbox:pdfbox-io:$pdfboxVersion"
@@ -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:8.12.6'
api 'org.simplejavamail:outlook-module:8.12.6' // MSG file support
api 'org.simplejavamail:simple-java-mail:9.2.0'
api 'org.simplejavamail:outlook-module:9.2.0' // MSG file support
api 'jakarta.mail:jakarta.mail-api:2.1.5'
runtimeOnly 'org.eclipse.angus:angus-mail:2.0.5'
@@ -1,5 +1,6 @@
package stirling.software.common.config.swagger;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
@@ -18,6 +19,7 @@ import stirling.software.common.model.tool.ToolFormat;
import stirling.software.common.model.tool.ToolIO;
import stirling.software.common.model.tool.ToolIOCase;
import stirling.software.common.model.tool.ToolIOWhen;
import stirling.software.common.service.ToolIOParameterDefaults;
/**
* Publishes each {@link ToolIO} into the spec as {@code x-stirling-io}, which is how the frontend
@@ -49,40 +51,42 @@ public class ToolIOOperationCustomizer
if (declaration == null) {
return operation;
}
operation.addExtension(EXTENSION_NAME, toExtension(declaration));
operation.addExtension(EXTENSION_NAME, toExtension(declaration, handlerMethod.getMethod()));
operation.setDescription(appendSummaryLine(operation.getDescription(), declaration));
return operation;
}
private static Map<String, Object> toExtension(ToolIO declaration) {
private static Map<String, Object> toExtension(ToolIO declaration, Method handler) {
Map<String, Object> extension = new LinkedHashMap<>();
extension.put("accepts", names(declaration.accepts()));
extension.put("produces", declaration.produces().name());
extension.put("arity", declaration.arity().name());
if (declaration.cases().length > 0) {
extension.put("cases", cases(declaration));
extension.put("cases", cases(declaration, handler));
}
return extension;
}
private static List<Map<String, Object>> cases(ToolIO declaration) {
return Arrays.stream(declaration.cases()).map(ToolIOOperationCustomizer::toCase).toList();
private static List<Map<String, Object>> cases(ToolIO declaration, Method handler) {
return Arrays.stream(declaration.cases()).map(rule -> toCase(rule, handler)).toList();
}
private static Map<String, Object> toCase(ToolIOCase rule) {
private static Map<String, Object> toCase(ToolIOCase rule, Method handler) {
Map<String, Object> entry = new LinkedHashMap<>();
entry.put(
"when",
Arrays.stream(rule.when()).map(ToolIOOperationCustomizer::toCondition).toList());
entry.put("when", Arrays.stream(rule.when()).map(c -> toCondition(c, handler)).toList());
entry.put("produces", rule.produces().name());
entry.put("arity", rule.arity().name());
return entry;
}
private static Map<String, Object> toCondition(ToolIOWhen condition) {
private static Map<String, Object> toCondition(ToolIOWhen condition, Method handler) {
Map<String, Object> entry = new LinkedHashMap<>();
entry.put("param", condition.param());
entry.put("matches", List.of(condition.matches()));
// The default the endpoint uses when this parameter is absent, so a step that never sends
// it still resolves. Omitted when the parameter is required with none.
ToolIOParameterDefaults.resolve(handler, condition.param())
.ifPresent(value -> entry.put("default", value));
return entry;
}
@@ -1104,6 +1104,13 @@ public class ApplicationProperties {
@Data
public static class Encryption {
private boolean enabled = false;
/**
* Emit an audit event for every decrypt of an encrypted blob. Compliance reviewers
* (HIPAA) expect read audit, so it defaults on; busy multi-user installs can disable.
* Denied decrypts and key lifecycle events are always audited regardless.
*/
private boolean auditReads = true;
}
@Data
@@ -5,13 +5,18 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
/** The runtime form of a {@link ToolIO} declaration, read off a handler method once at startup. */
public record ToolIOSpec(
Set<ToolFormat> accepts, ToolFormat produces, ToolArity arity, List<Case> cases) {
public record When(String param, List<String> matches) {
/**
* @param paramDefault the value used when the parameter is absent, or null when it has no
* default - an absent parameter then leaves the case unresolved rather than defaulted.
*/
public record When(String param, List<String> matches, String paramDefault) {
boolean holdsFor(Object value) {
String normalised = normalise(value);
@@ -19,6 +24,14 @@ public record ToolIOSpec(
}
}
/** Supplies the default value a request parameter takes when a caller omits it. */
@FunctionalInterface
public interface ParameterDefaults {
Optional<String> defaultFor(String param);
ParameterDefaults NONE = param -> Optional.empty();
}
/**
* Both sides of a condition are normalised at comparison, not at construction: the declaration
* reaches the frontend and the engine as published data, and normalising only one side there
@@ -44,25 +57,33 @@ public record ToolIOSpec(
}
public static ToolIOSpec from(ToolIO annotation) {
return from(annotation, ParameterDefaults.NONE);
}
public static ToolIOSpec from(ToolIO annotation, ParameterDefaults defaults) {
return new ToolIOSpec(
new LinkedHashSet<>(Arrays.asList(annotation.accepts())),
annotation.produces(),
annotation.arity(),
Arrays.stream(annotation.cases()).map(ToolIOSpec::toCase).toList());
Arrays.stream(annotation.cases()).map(rule -> toCase(rule, defaults)).toList());
}
private static Case toCase(ToolIOCase rule) {
List<When> when = Arrays.stream(rule.when()).map(ToolIOSpec::toWhen).toList();
private static Case toCase(ToolIOCase rule, ParameterDefaults defaults) {
List<When> when = Arrays.stream(rule.when()).map(c -> toWhen(c, defaults)).toList();
return new Case(when, rule.produces(), rule.arity());
}
private static When toWhen(ToolIOWhen condition) {
return new When(condition.param(), List.of(condition.matches()));
private static When toWhen(ToolIOWhen condition, ParameterDefaults defaults) {
return new When(
condition.param(),
List.of(condition.matches()),
defaults.defaultFor(condition.param()).orElse(null));
}
/**
* First matching {@link Case} wins. If none match but one reads a parameter we cannot see, the
* declared output comes back uncertain: a value we never saw might have picked another branch.
* First matching {@link Case} wins. A parameter the caller omitted resolves to its declared
* default; only a parameter with no default leaves the output uncertain, since an unseen value
* might then have picked another branch.
*
* @param parameters the step's configured parameters, or null when not known
*/
@@ -71,12 +92,17 @@ public record ToolIOSpec(
for (Case rule : cases) {
boolean allHold = true;
for (When condition : rule.when()) {
if (parameters == null || !parameters.containsKey(condition.param())) {
Object value;
if (parameters != null && parameters.containsKey(condition.param())) {
value = parameters.get(condition.param());
} else if (condition.paramDefault() != null) {
value = condition.paramDefault();
} else {
sawUnknownParam = true;
allHold = false;
continue;
}
allHold &= condition.holdsFor(parameters.get(condition.param()));
allHold &= condition.holdsFor(value);
}
if (allHold) {
return new Output(rule.produces(), rule.arity(), true);
@@ -17,7 +17,6 @@ import java.util.concurrent.Semaphore;
import java.util.function.Consumer;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.examples.util.DeletingRandomAccessFile;
import org.apache.pdfbox.io.IOUtils;
import org.apache.pdfbox.io.MemoryUsageSetting;
import org.apache.pdfbox.io.RandomAccessReadBufferedFile;
@@ -31,6 +30,7 @@ import org.springframework.web.multipart.MultipartFile;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.util.DeletingRandomAccessFile;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.TempFileManager;
@@ -0,0 +1,92 @@
package stirling.software.common.service;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Optional;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
/**
* The value a request parameter takes when the caller omits it, read from the request model so a
* {@code @ToolIOCase} can be resolved even for a step that never sends the parameter it branches
* on. The default is read from the field so it cannot drift.
*/
@Slf4j
public final class ToolIOParameterDefaults {
// Swagger's sentinel for an unset @Schema string member; not a real default value.
private static final String SCHEMA_UNSET = "##default";
private ToolIOParameterDefaults() {}
/**
* The default {@code param} resolves to when absent, or empty when the parameter is required
* with no declared default - in which case an unset value leaves the output genuinely unknown
* rather than defaulted, and the chain reports it as uncertain.
*
* <p>Precedence: an explicit {@code @Schema(defaultValue)}, then the field's own value (a
* primitive's language default, or an initializer), then the empty string for an optional field
* left null, and finally empty for a required field with none of the above.
*/
public static Optional<String> resolve(Method handler, String param) {
for (Parameter parameter : handler.getParameters()) {
Field field = findField(parameter.getType(), param);
if (field != null) {
return fromField(parameter.getType(), field);
}
}
return Optional.empty();
}
private static Optional<String> fromField(Class<?> owner, Field field) {
Schema schema = field.getAnnotation(Schema.class);
if (schema != null
&& !schema.defaultValue().isEmpty()
&& !SCHEMA_UNSET.equals(schema.defaultValue())) {
return Optional.of(schema.defaultValue());
}
Object value = readField(owner, field);
if (value != null) {
return Optional.of(String.valueOf(value));
}
return isRequired(field, schema) ? Optional.empty() : Optional.of("");
}
private static Object readField(Class<?> owner, Field field) {
try {
Object instance = owner.getDeclaredConstructor().newInstance();
field.setAccessible(true);
return field.get(instance);
} catch (ReflectiveOperationException | RuntimeException e) {
// A request model we cannot instantiate leaves the default unknown, which the check
// treats conservatively as uncertain. Never break startup over it.
log.warn("Could not read default of {}.{}", owner.getSimpleName(), field.getName(), e);
return null;
}
}
private static boolean isRequired(Field field, Schema schema) {
if (schema != null && schema.requiredMode() == Schema.RequiredMode.REQUIRED) {
return true;
}
return field.isAnnotationPresent(NotNull.class)
|| field.isAnnotationPresent(NotBlank.class);
}
private static Field findField(Class<?> type, String name) {
for (Class<?> c = type; c != null && c != Object.class; c = c.getSuperclass()) {
try {
return c.getDeclaredField(name);
} catch (NoSuchFieldException ignored) {
// Try the superclass; request models extend a shared file-input base.
}
}
return null;
}
}
@@ -67,7 +67,10 @@ public class ToolIORegistry implements ToolMetadataService, ToolIOSource {
if (annotation == null) {
return;
}
ToolIOSpec spec = ToolIOSpec.from(annotation);
Method method = handler.getMethod();
ToolIOSpec spec =
ToolIOSpec.from(
annotation, param -> ToolIOParameterDefaults.resolve(method, param));
for (String pattern : extractPatterns(info)) {
target.put(pattern, spec);
}
@@ -1,4 +1,4 @@
package org.apache.pdfbox.examples.util;
package stirling.software.common.util;
import java.io.File;
import java.io.IOException;
@@ -194,7 +194,7 @@ public class EmlParser {
}
attachment.setFilename(filename);
String contentId = embedded ? stripCid(resourceName) : null;
String contentId = embedded ? stripCid(resource.getContentId()) : null;
attachment.setContentId(contentId);
String detectedContentType = EmlProcessingUtils.detectMimeType(filename, contentType);
@@ -101,7 +101,12 @@ class ToolChainValidatorConformanceTest {
for (JsonNode match : condition.get("matches")) {
matches.add(match.asString());
}
when.add(new ToolIOSpec.When(condition.get("param").asString(), matches));
JsonNode paramDefault = condition.get("default");
when.add(
new ToolIOSpec.When(
condition.get("param").asString(),
matches,
paramDefault == null ? null : paramDefault.asString()));
}
cases.add(
new ToolIOSpec.Case(
+4 -4
View File
@@ -156,7 +156,7 @@ if (buildWithPortal) {
}
// Workspace root holds package.json and node_modules (shared across editor /
// future portal). Editor-specific paths (src, public, dist, tauri) live one
// level deeper under frontend/.
// level deeper under frontend/editor/.
// Vite mode: -PprototypesMode > -PfrontendMode > enableSaas > disableAdditional > proprietary.
def frontendModeOverride = project.findProperty('frontendMode')?.toString()?.toLowerCase()
@@ -176,11 +176,11 @@ def frontendBuildTask = "frontend:build:${frontendMode}"
// Workspace root holds package.json and node_modules (shared across editor /
// future portal). Editor-specific paths (src, public, dist, tauri) live one
// level deeper under frontend/. When the portal lands as an embedded
// level deeper under frontend/editor/. When the portal lands as an embedded
// app, add a sibling frontendPortalDir / frontendPortalDistDir alongside.
def frontendDir = file('../../frontend')
def frontendEditorDir = file('../../frontend')
def frontendEditorDistDir = file('../../frontend/dist')
def frontendEditorDir = file('../../frontend/editor')
def frontendEditorDistDir = file('../../frontend/editor/dist')
def resourcesStaticDir = file('src/main/resources/static')
def generatedFrontendPaths = [
'assets',
@@ -61,6 +61,7 @@ public class PdfOverlayController {
int overlayPos = request.getOverlayPosition();
MultipartFile[] overlayFiles = request.getOverlayFiles();
validateOverlayFiles(overlayFiles);
File[] overlayPdfFiles = new File[overlayFiles.length];
List<File> tempFiles = new ArrayList<>(); // List to keep track of temporary files
@@ -120,10 +121,29 @@ public class PdfOverlayController {
}
}
// Both fields are declared required, but @ModelAttribute binding leaves them null when the
// caller omits them, which would otherwise surface as a 500 instead of a 400.
private void validateOverlayFiles(MultipartFile[] overlayFiles) {
if (overlayFiles == null || overlayFiles.length == 0) {
throw ExceptionUtils.createIllegalArgumentException(
"error.overlayFilesRequired", "At least one overlay file is required");
}
for (MultipartFile overlayFile : overlayFiles) {
if (overlayFile == null || overlayFile.isEmpty()) {
throw ExceptionUtils.createIllegalArgumentException(
"error.overlayFileEmpty", "Overlay files must not be empty");
}
}
}
private Map<Integer, String> prepareOverlayGuide(
int basePageCount, File[] overlayFiles, String mode, int[] counts, List<File> tempFiles)
throws IOException {
Map<Integer, String> overlayGuide = new HashMap<>();
if (mode == null) {
throw ExceptionUtils.createIllegalArgumentException(
"error.invalidFormat", "Invalid {0} format: {1}", "overlay mode", "null");
}
switch (mode) {
case "SequentialOverlay":
sequentialOverlay(overlayGuide, overlayFiles, basePageCount, tempFiles);
@@ -13,7 +13,6 @@ import java.util.Calendar;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.apache.pdfbox.examples.signature.CreateSignatureBase;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
@@ -76,6 +75,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest;
import stirling.software.SPDF.pdf.signature.CreateSignatureBase;
import stirling.software.SPDF.service.HardwareKeyStoreService;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.enumeration.ResourceWeight;
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.pdfbox.examples.signature;
package stirling.software.SPDF.pdf.signature;
import java.io.IOException;
import java.io.InputStream;
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.apache.pdfbox.examples.signature;
package stirling.software.SPDF.pdf.signature;
import java.io.IOException;
import java.io.InputStream;
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.pdfbox.examples.signature;
package stirling.software.SPDF.pdf.signature;
import java.io.IOException;
import java.io.InputStream;
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.pdfbox.examples.signature;
package stirling.software.SPDF.pdf.signature;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.pdfbox.examples.util;
package stirling.software.SPDF.utils;
import java.io.IOException;
import java.io.InputStream;
@@ -292,26 +292,15 @@ storage:
linkExpirationDays: 3 # Number of days before share links expire
signing:
enabled: false # set to 'true' to enable group signing workflow (requires storage.enabled) [ALPHA]
# ====================================================================================
# ENCRYPTION AT REST - PRO / ENTERPRISE LICENSE REQUIRED TO ENABLE
# ====================================================================================
# Encrypts stored files (AES-256 envelope encryption, per-team keys). The master key is
# resolved in this order:
# 1. stirling.security.fileEncryptionKey property
# 2. STIRLING_FILE_ENCRYPTION_KEY environment variable
# 3. an auto-generated configs/file-encryption.key (single-node only; cluster mode
# requires an explicitly shared key on every node)
# Generate a key with: openssl rand -base64 32
#
# *** BACK UP THE MASTER KEY. Losing it makes every encrypted stored file ***
# *** permanently unrecoverable. Verify backups against the key fingerprint logged ***
# *** at startup. ***
#
# Enabling encrypts new writes only (existing files stay readable as plaintext).
# Disabling later only stops encrypting new writes - existing encrypted files remain
# readable as long as the key material is present.
# Encryption at rest for stored files (AES-256, per-team keys). Requires a Pro or
# Enterprise licence. Key setup, cluster requirements, the encrypt-existing migration,
# the revocation kill switch and master-key rotation are documented in
# devGuide/STORAGE_ENCRYPTION_AT_REST.md
# WARNING: back up the master key (configs/file-encryption.key by default) - losing it
# makes every encrypted stored file permanently unrecoverable.
encryption:
enabled: false # set to 'true' to encrypt stored files at rest
auditReads: true # audit every decrypt of an encrypted file (denied decrypts and key lifecycle events are always audited). NOTE: audit events require an Enterprise licence; encryption itself works on Pro.
userListScope: org # Signing user-picker scope: 'org' (default) = whole instance, else caller's team only.
autoPipeline:
outputFolder: "" # Output folder for processed pipeline files (leave empty for default)
@@ -12,10 +12,11 @@ To convert a PDF file to a single WebP image:
To adjust the DPI resolution for rendering PDF pages:
python script.py input.pdf output_directory --dpi 150
"""
""" # noqa: E501
import argparse
import os
from pdf2image import convert_from_path
from PIL import Image
@@ -53,14 +54,12 @@ def resize_image(input_image_path, output_image_path, max_size=(16383, 16383)):
# Resize the image
resized_image = image.resize((new_width, new_height), Image.LANCZOS)
resized_image.save(output_image_path, format="WEBP", quality=100)
print(
f"The image was successfully resized to ({new_width}, {new_height}) and saved as WebP: {output_image_path}"
)
print(f"The image was successfully resized to ({new_width}, {new_height}) and saved as WebP: {output_image_path}")
else:
# If dimensions are within the allowed limits, save the image directly
image.save(output_image_path, format="WEBP", quality=100)
print(f"The image was successfully saved as WebP: {output_image_path}")
except Exception as e:
except Exception as e: # noqa: BLE001
print(f"An error occurred: {e}")
@@ -149,9 +148,7 @@ def main(pdf_image_path, output_dir, dpi=300, single_images_flag=False):
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Convert a PDF file to WebP images.")
parser.add_argument("pdf_path", help="The path to the input PDF file.")
parser.add_argument(
"output_dir", help="The directory where the WebP images should be saved."
)
parser.add_argument("output_dir", help="The directory where the WebP images should be saved.")
parser.add_argument(
"--dpi",
type=int,
@@ -1,8 +1,9 @@
import argparse
import sys
import os
import cv2
import numpy as np
import os
def find_photo_boundaries(image, background_color, tolerance=30, min_area=10000, min_contour_area=500):
mask = cv2.inRange(image, background_color - tolerance, background_color + tolerance)
@@ -57,7 +58,7 @@ def auto_rotate(image, angle_threshold=1):
(h, w) = image.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
M = cv2.getRotationMatrix2D(center, angle, 1.0) # noqa: N806
return cv2.warpAffine(image, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)
@@ -75,12 +76,12 @@ def crop_borders(image, border_color, tolerance=30):
return image[y:y+h, x:x+w]
def split_photos(input_file, output_directory, tolerance=30, min_area=10000, min_contour_area=500, angle_threshold=10, border_size=0):
def split_photos(input_file, output_directory, tolerance=30, min_area=10000, min_contour_area=500, angle_threshold=10, border_size=0): # noqa: E501
image = cv2.imread(input_file)
background_color = estimate_background_color(image)
# Add a constant border around the image
image = cv2.copyMakeBorder(image, border_size, border_size, border_size, border_size, cv2.BORDER_CONSTANT, value=background_color)
image = cv2.copyMakeBorder(image, border_size, border_size, border_size, border_size, cv2.BORDER_CONSTANT, value=background_color) # noqa: E501
photo_boundaries = find_photo_boundaries(image, background_color, tolerance)
@@ -111,12 +112,12 @@ if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Split photos in an image")
parser.add_argument("input_file", help="The input scanned image containing multiple photos.")
parser.add_argument("output_directory", help="The directory where the result images should be placed.")
parser.add_argument("--tolerance", type=int, default=30, help="Determines the range of color variation around the estimated background color (default: 30).")
parser.add_argument("--min_area", type=int, default=10000, help="Sets the minimum area threshold for a photo (default: 10000).")
parser.add_argument("--min_contour_area", type=int, default=500, help="Sets the minimum contour area threshold for a photo (default: 500).")
parser.add_argument("--angle_threshold", type=int, default=10, help="Sets the minimum absolute angle required for the image to be rotated (default: 10).")
parser.add_argument("--border_size", type=int, default=0, help="Sets the size of the border added and removed to prevent white borders in the output (default: 0).")
parser.add_argument("--tolerance", type=int, default=30, help="Determines the range of color variation around the estimated background color (default: 30).") # noqa: E501
parser.add_argument("--min_area", type=int, default=10000, help="Sets the minimum area threshold for a photo (default: 10000).") # noqa: E501
parser.add_argument("--min_contour_area", type=int, default=500, help="Sets the minimum contour area threshold for a photo (default: 500).") # noqa: E501
parser.add_argument("--angle_threshold", type=int, default=10, help="Sets the minimum absolute angle required for the image to be rotated (default: 10).") # noqa: E501
parser.add_argument("--border_size", type=int, default=0, help="Sets the size of the border added and removed to prevent white borders in the output (default: 0).") # noqa: E501
args = parser.parse_args()
split_photos(args.input_file, args.output_directory, tolerance=args.tolerance, min_area=args.min_area, min_contour_area=args.min_contour_area, angle_threshold=args.angle_threshold, border_size=args.border_size)
split_photos(args.input_file, args.output_directory, tolerance=args.tolerance, min_area=args.min_area, min_contour_area=args.min_contour_area, angle_threshold=args.angle_threshold, border_size=args.border_size) # noqa: E501
@@ -26,6 +26,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import stirling.software.common.model.tool.ToolFormat;
import stirling.software.common.model.tool.ToolIO;
import stirling.software.common.model.tool.ToolIOSpec;
import stirling.software.common.service.ToolIOParameterDefaults;
/**
* Every document-transforming endpoint must declare its I/O, or it becomes a hole in the
@@ -233,6 +234,32 @@ class ToolIODeclarationCoverageTest {
assertEquals(Set.of("ps", "pcl", "xps"), declared);
}
@Test
void anAbsentParameterResolvesToItsRequestModelDefault() {
// A pipeline step often omits a parameter a case branches on. The default is read from the
// request model, so the output resolves anyway instead of coming back uncertain.
// Auto Rotate never sends dryRun; its default (false) means the JSON branch cannot fire.
assertEquals(
ToolFormat.PDF,
spec("/api/v1/misc/auto-rotate-pdf").resolveOutput(Map.of()).format());
assertTrue(spec("/api/v1/misc/auto-rotate-pdf").resolveOutput(Map.of()).certain());
// Change Permissions posts to add-password with no password fields; both default to blank,
// so the unencrypted branch fires and it is not mistaken for producing an encrypted PDF.
assertEquals(
ToolFormat.PDF,
spec("/api/v1/security/add-password").resolveOutput(Map.of()).format());
assertTrue(spec("/api/v1/security/add-password").resolveOutput(Map.of()).certain());
}
@Test
void aRequiredParameterWithNoDefaultStaysUncertainWhenAbsent() {
// pdf/text branches on outputFormat, which is required with no default. Absent, its output
// is genuinely txt-or-rtf-dependent, so it must remain uncertain rather than assume TEXT.
assertFalse(spec("/api/v1/convert/pdf/text").resolveOutput(Map.of()).certain());
}
@Test
void onlyRemovePasswordAcceptsAnEncryptedDocument() {
assertTrue(
@@ -288,7 +315,11 @@ class ToolIODeclarationCoverageTest {
}
required.add(full);
if (declaration != null) {
declared.put(full, ToolIOSpec.from(declaration));
declared.put(
full,
ToolIOSpec.from(
declaration,
param -> ToolIOParameterDefaults.resolve(method, param)));
}
}
}
@@ -1,4 +1,4 @@
package org.apache.pdfbox.examples.signature;
package stirling.software.SPDF.pdf.signature;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -1,4 +1,4 @@
package org.apache.pdfbox.examples.signature;
package stirling.software.SPDF.pdf.signature;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -1,4 +1,4 @@
package org.apache.pdfbox.examples.util;
package stirling.software.SPDF.utils;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
@@ -2,7 +2,7 @@ package stirling.software.proprietary.access.model;
/** Types of resources whose access can be gated by {@link ResourceGrant}. */
public enum ResourceType {
// The admin portal / processor (frontend/src/processor/proprietary). Singleton resource (empty
// The admin portal / processor (frontend/editor/src/portal). Singleton resource (empty
// resourceId).
PORTAL,
// A stored S3/MCP/API integration configuration.
@@ -19,6 +19,9 @@ public enum AuditEventType {
// File operations - STANDARD level
FILE_OPERATION("File operation"),
// Storage encryption at rest - STANDARD level
STORAGE_ENCRYPTION("Storage encryption operation"),
// PDF operations - STANDARD level
PDF_PROCESS("PDF processing operation"),
@@ -0,0 +1,39 @@
package stirling.software.proprietary.failure;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Component;
import lombok.RequiredArgsConstructor;
/**
* "Seen, and I own it." Moves {@code NEW} to {@code ACKNOWLEDGED} so the row stops counting as
* unread while staying in the open list. Touches only the status, not the document, ledger or run.
*
* <p>Re-acknowledging is a no-op that keeps the original actor and timestamp, so the first person
* to pick it up stays credited.
*/
@Component
@RequiredArgsConstructor
public class AcknowledgeAction implements FailureAction {
private final FileRunEventStore store;
@Override
public FailureActionId id() {
return FailureActionId.ACKNOWLEDGE;
}
@Override
public FileRunEvent execute(FileRunEvent event, Map<String, String> inputs, String actor) {
// Guarded on NEW so a racing acknowledger cannot re-stamp the row, and so re-acknowledging
// returns the first actor's row rather than taking their credit.
return store.applyStatusOnce(
event.id(),
event.teamId(),
FileRunEventStatus.ACKNOWLEDGED,
actor,
List.of(FileRunEventStatus.NEW));
}
}
@@ -0,0 +1,32 @@
package stirling.software.proprietary.failure;
import java.util.Map;
import org.springframework.stereotype.Component;
import lombok.RequiredArgsConstructor;
/**
* "No remediation will happen; close it." Terminal. Named Dismiss rather than Reject because it
* closes the incident, not the document: nothing is deleted or delivered, the ledger row is left
* alone, and a different file failing the same way still opens its own incident.
*
* <p>Recurrences of this exact failure fold onto the dismissed row, which is what makes "stop
* showing me this" hold for a source that re-lists the same failing file each sweep.
*/
@Component
@RequiredArgsConstructor
public class DismissAction implements FailureAction {
private final FileRunEventStore store;
@Override
public FailureActionId id() {
return FailureActionId.DISMISS;
}
@Override
public FileRunEvent execute(FileRunEvent event, Map<String, String> inputs, String actor) {
return store.applyStatus(event.id(), event.teamId(), FileRunEventStatus.DISMISSED, actor);
}
}
@@ -0,0 +1,23 @@
package stirling.software.proprietary.failure;
import java.util.Map;
/**
* The behaviour behind one {@link FailureActionId}. Implementations are Spring beans injected as a
* {@code List} and resolved by id, the pattern already used for {@code InputSource}, {@code
* PolicyOutputSink} and {@code PolicyTrigger}.
*
* <p>Keeping behaviour out of the registry keeps that pure data, so a new kind ships by declaring
* an action id that already has a handler.
*/
public interface FailureAction {
FailureActionId id();
/**
* Apply the action and return the updated event. {@code inputs} carries whatever the action
* declared it needs, which is nothing for the two that exist today. Implementations leave the
* document, ledger, run and output destinations alone unless that is the action's purpose.
*/
FileRunEvent execute(FileRunEvent event, Map<String, String> inputs, String actor);
}
@@ -0,0 +1,49 @@
package stirling.software.proprietary.failure;
import lombok.Getter;
/**
* Why an action could not be dispatched. Carries a {@link Reason} rather than an HTTP status, so
* the service stays web-agnostic and the controller owns the mapping.
*/
@Getter
public class FailureActionException extends RuntimeException {
public enum Reason {
/**
* No such event, it belongs to another team, or the caller's team did not resolve. One
* reason for all three, so the response does not vary with which it was. Unrelated to
* {@link FailureKind#UNKNOWN}, which is an unclassified failure rather than a refused
* action.
*/
EVENT_NOT_FOUND,
/** The action id is not in the vocabulary at all, or has no registered handler. */
ACTION_NOT_RECOGNISED,
/**
* The action exists but this kind does not declare it, so an incoherent pairing (releasing
* a document whose destination is what failed) cannot be dispatched even by hand.
*
* <p>Unreachable today: both kinds declare both actions, so no request can trip this guard
* until a kind ships with a restricted action set. Declared now because the guard must
* exist before that kind does, not after.
*/
ACTION_NOT_DECLARED,
/** The event is already closed, so no further transition is possible. */
ALREADY_CLOSED
}
private final Reason reason;
public FailureActionException(Reason reason, String message) {
this(reason, message, null);
}
/** For a refusal that follows from a lower-level failure, so its stack is not dropped. */
public FailureActionException(Reason reason, String message, Throwable cause) {
super(message, cause);
this.reason = reason;
}
}
@@ -0,0 +1,11 @@
package stirling.software.proprietary.failure;
/**
* The actions a {@link FailureKind} may declare. Both are incident dispositions: they change how
* the event is shown and touch nothing else, which is what makes them valid for every kind
* including {@link FailureKind#UNKNOWN}, and why there is no {@code APPROVE} yet.
*/
public enum FailureActionId {
ACKNOWLEDGE,
DISMISS
}
@@ -0,0 +1,68 @@
package stirling.software.proprietary.failure;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.stereotype.Service;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
/**
* Resolves a {@link FailureActionId} to the bean that implements it. The startup check is the
* point: because kinds declare action ids as data, one could name an action nobody implements,
* which would otherwise show up as a button that 400s rather than as a failed boot.
*/
@Slf4j
@Service
public class FailureActionRegistry {
private final Map<FailureActionId, FailureAction> byId = new EnumMap<>(FailureActionId.class);
public FailureActionRegistry(List<FailureAction> actions) {
for (FailureAction action : actions) {
FailureAction clash = byId.put(action.id(), action);
if (clash != null) {
throw new IllegalStateException(
"Two handlers registered for action "
+ action.id()
+ ": "
+ clash.getClass().getName()
+ " and "
+ action.getClass().getName());
}
}
}
/**
* Fail fast if any kind declares an action with no handler, naming every gap rather than the
* first, so one boot tells you everything that is missing.
*/
@PostConstruct
void verifyEveryDeclaredActionHasAHandler() {
List<String> gaps =
Arrays.stream(FailureKind.values())
.flatMap(
kind ->
kind.getActions().stream()
.filter(action -> !byId.containsKey(action))
.map(action -> kind.getId() + " -> " + action))
.toList();
if (!gaps.isEmpty()) {
throw new IllegalStateException(
"Failure kinds declare actions with no registered handler: " + gaps);
}
log.debug(
"Failure action registry initialised with {} handler(s) for {} kind(s)",
byId.size(),
FailureKind.values().length);
}
public Optional<FailureAction> find(FailureActionId id) {
return Optional.ofNullable(byId.get(id));
}
}
@@ -0,0 +1,109 @@
package stirling.software.proprietary.failure;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClientResponseException;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.util.ExceptionUtils;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
/**
* Maps a thrown failure onto a {@link FailureKind}. A tool's 4xx arrives as a {@link
* RestClientResponseException} whose body is the Problem Details document carrying {@code
* errorCode}, so this matches on codes rather than exception messages.
*
* <p>Anything unrecognised becomes {@link FailureKind#UNKNOWN}, so every failed run still gets a
* record.
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class FailureClassifier {
/** Set by {@code GlobalExceptionHandler#createProblemDetailResponse}. */
private static final String ERROR_CODE_PROPERTY = "errorCode";
/**
* Depth bound on the cause chain. The JDK forbids self-causation but not a longer cycle (A
* caused by B caused by A), which an unbounded walk would spin on. Real chains are a handful
* deep.
*/
private static final int MAX_CAUSE_DEPTH = 16;
private final ObjectMapper objectMapper;
/**
* Refuse to start on an ambiguous registry: two kinds claiming one code would make {@link
* #classify} depend on declaration order. Checked here because this is what resolves codes to
* kinds, and at boot so the message names the codes rather than arriving as a class-init error.
*/
@PostConstruct
void verifyNoErrorCodeIsClaimedTwice() {
List<String> duplicates = FailureKind.duplicateErrorCodes();
if (!duplicates.isEmpty()) {
throw new IllegalStateException(
"Error codes claimed by more than one failure kind: " + duplicates);
}
}
/** Never null, never throws. A classifier that can fail would lose the failure it describes. */
public FailureKind classify(Throwable throwable) {
Throwable current = throwable;
for (int depth = 0; current != null && depth < MAX_CAUSE_DEPTH; depth++) {
FailureKind resolved = classifyOne(current);
if (resolved != FailureKind.UNKNOWN) {
return resolved;
}
Throwable cause = current.getCause();
current = cause == current ? null : cause;
}
return FailureKind.UNKNOWN;
}
private FailureKind classifyOne(Throwable throwable) {
// A tool step's 4xx/5xx: the Problem Details body names the error code.
if (throwable instanceof RestClientResponseException responseException) {
String code = errorCodeFromBody(responseException);
if (code != null) {
return FailureKind.byErrorCode(code).orElse(FailureKind.UNKNOWN);
}
}
// Thrown in-process (not over loopback): the exception carries its own code.
if (throwable instanceof ExceptionUtils.ErrorCodeProvider provider) {
return FailureKind.byErrorCode(provider.getErrorCode()).orElse(FailureKind.UNKNOWN);
}
return FailureKind.UNKNOWN;
}
/**
* Pull {@code errorCode} from a Problem Details body, or null when the body is absent, not
* JSON, or has no such property (an entitlement sentinel has {@code error} instead).
*/
private String errorCodeFromBody(RestClientResponseException exception) {
String body = exception.getResponseBodyAsString();
if (body == null || body.isBlank()) {
return null;
}
try {
JsonNode root = objectMapper.readTree(body);
JsonNode code = root.get(ERROR_CODE_PROPERTY);
if (code == null || !code.isTextual()) {
return null;
}
String text = code.asString();
return text.isBlank() ? null : text;
} catch (JacksonException e) {
log.debug("Downstream error body was not JSON; classifying as UNKNOWN");
return null;
}
}
}
@@ -0,0 +1,236 @@
package stirling.software.proprietary.failure;
import static stirling.software.proprietary.failure.FailureActionId.ACKNOWLEDGE;
import static stirling.software.proprietary.failure.FailureActionId.DISMISS;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import lombok.AccessLevel;
import lombok.Getter;
/**
* The registry of failure kinds, described as data: a stable id, i18n keys and an English fallback
* like {@code ExceptionUtils.ErrorCode}, plus the facets a review surface needs.
*
* <p>Actions are declared here but implemented in {@link FailureAction} beans resolved by id, so a
* new kind ships as a registry entry plus copy. Two members today: {@link #UNKNOWN} gives every
* failed run a record, and kinds get promoted out of it as production shows what occurs.
*/
@Getter
public enum FailureKind {
INPUT_PASSWORD_PROTECTED(
FailureStage.INPUT,
FailureSeverity.ERROR,
FailureRemedy.NEEDS_USER_INPUT,
FailureScope.FILE,
errorCodes("E004"),
fallback("This document is password-protected, so the pipeline could not read it."),
offer(ACKNOWLEDGE),
offer(DISMISS, "dismissSkipFile")),
UNKNOWN(
FailureStage.INTERNAL,
FailureSeverity.ERROR,
FailureRemedy.PERMANENT,
FailureScope.RUN,
noErrorCodes(),
fallback("This run failed for a reason Stirling does not yet recognise."),
offer(ACKNOWLEDGE),
offer(DISMISS));
private static final String KEY_PREFIX = "portal.failures.kind.";
private static final String ACTION_KEY_PREFIX = "portal.failures.action.";
/**
* Every claimed {@code ErrorCode}, to the kind claiming it. Indexed once rather than scanned
* per lookup, so a duplicate cannot be resolved by declaration order without anyone noticing. A
* duplicate is refused at boot; see {@link #duplicateErrorCodes()}.
*/
private static final Map<String, FailureKind> BY_ERROR_CODE = indexErrorCodes();
private final FailureStage stage;
private final FailureSeverity severity;
private final FailureRemedy remedy;
private final FailureScope scope;
/** English fallback, used when the client has no translation for {@link #getTitleKey()}. */
private final String defaultTitle;
/** Codes from {@code ExceptionUtils.ErrorCode} that map onto this kind; may be empty. */
private final List<String> errorCodes;
/**
* What the review surface may offer, in display order. Never empty. No generated accessor,
* because {@link Offer} is private; read via {@link #getActions()} or {@link #labelKeyFor}.
*/
@Getter(AccessLevel.NONE)
private final List<Offer> offers;
FailureKind(
FailureStage stage,
FailureSeverity severity,
FailureRemedy remedy,
FailureScope scope,
List<String> errorCodes,
String defaultTitle,
Offer... offers) {
this.stage = stage;
this.severity = severity;
this.remedy = remedy;
this.scope = scope;
this.errorCodes = List.copyOf(errorCodes);
this.defaultTitle = defaultTitle;
this.offers = List.of(offers);
}
/**
* One action this kind offers, with the key to label it by. One ordered list rather than ids
* plus a parallel map of overrides, which could disagree with each other.
*
* @param labelKeySuffix key under {@code portal.failures.action.}, or null for the generic
* label
*/
private record Offer(FailureActionId id, String labelKeySuffix) {}
/** An action labelled by this kind's own wording, where the generic label reads badly. */
private static Offer offer(FailureActionId id, String labelKeySuffix) {
return new Offer(id, labelKeySuffix);
}
/** An action labelled by the shared wording for that action. */
private static Offer offer(FailureActionId id) {
return new Offer(id, null);
}
/**
* The {@code ErrorCode}s this kind claims. Java has no named arguments, so these factories
* exist to label the two constructor arguments whose types do not already name themselves.
*/
private static List<String> errorCodes(String... codes) {
return List.of(codes);
}
/** Claims no {@code ErrorCode}: reached only through the classifier's fallback. */
private static List<String> noErrorCodes() {
return List.of();
}
/** The English text shown when the client has no translation for this kind. */
private static String fallback(String englishTitle) {
return englishTitle;
}
/** Stable wire id. Never renamed once shipped: persisted rows reference it. */
public String getId() {
return name();
}
public String getTitleKey() {
return KEY_PREFIX + lowerCamelId() + ".title";
}
public String getDescriptionKey() {
return KEY_PREFIX + lowerCamelId() + ".description";
}
/** The actions this kind offers, in display order. */
public List<FailureActionId> getActions() {
return offers.stream().map(Offer::id).toList();
}
/** Whether this kind offers {@code action}. The dispatch guard: see {@code FailureActionId}. */
public boolean declares(FailureActionId action) {
return offers.stream().anyMatch(offer -> offer.id() == action);
}
/** The label key for {@code action}: this kind's own wording, else the generic one. */
public String labelKeyFor(FailureActionId action) {
return offers.stream()
.filter(offer -> offer.id() == action && offer.labelKeySuffix() != null)
.map(offer -> ACTION_KEY_PREFIX + offer.labelKeySuffix())
.findFirst()
.orElseGet(() -> genericLabelKey(action));
}
/** The label key used when a kind supplies no wording of its own. */
static String genericLabelKey(FailureActionId action) {
return ACTION_KEY_PREFIX + toLowerCamel(action.name());
}
/**
* Lookup by wire id. Empty rather than throwing: ids arrive from persisted rows and clients.
*/
public static Optional<FailureKind> byId(String id) {
if (id == null || id.isBlank()) {
return Optional.empty();
}
return Arrays.stream(values()).filter(kind -> kind.name().equals(id)).findFirst();
}
private static Map<String, FailureKind> indexErrorCodes() {
Map<String, FailureKind> index = new HashMap<>();
for (FailureKind kind : values()) {
for (String code : kind.errorCodes) {
index.putIfAbsent(code, kind);
}
}
return Map.copyOf(index);
}
/**
* Codes claimed by more than one kind, which would make classification depend on declaration
* order. Empty in a well-formed registry.
*
* <p>Reported for a caller to act on rather than thrown from class init, where it would arrive
* as an {@code ExceptionInInitializerError} blamed on whatever touched the enum first, then as
* {@code NoClassDefFoundError} everywhere after. {@link FailureClassifier} refuses to start.
*/
static List<String> duplicateErrorCodes() {
return duplicatesIn(Arrays.stream(values()).flatMap(kind -> kind.errorCodes.stream()));
}
/**
* The codes appearing more than once, first-seen order. Split out from the registry because the
* registry is a closed enum: this is the only seam at which the detection itself can be shown
* to find anything.
*/
static List<String> duplicatesIn(Stream<String> codes) {
Set<String> seen = new HashSet<>();
return codes.filter(code -> !seen.add(code)).distinct().toList();
}
/** The kind claiming {@code errorCode}, if any. Empty for a code no kind has adopted yet. */
public static Optional<FailureKind> byErrorCode(String errorCode) {
if (errorCode == null || errorCode.isBlank()) {
return Optional.empty();
}
return Optional.ofNullable(BY_ERROR_CODE.get(errorCode));
}
private String lowerCamelId() {
return toLowerCamel(name());
}
/**
* {@code INPUT_PASSWORD_PROTECTED} to {@code inputPasswordProtected}, for i18n key building.
*/
private static String toLowerCamel(String screamingSnake) {
String[] parts = screamingSnake.toLowerCase(Locale.ROOT).split("_");
StringBuilder out = new StringBuilder(parts[0]);
for (int i = 1; i < parts.length; i++) {
if (parts[i].isEmpty()) {
continue;
}
out.append(Character.toUpperCase(parts[i].charAt(0))).append(parts[i].substring(1));
}
return out.toString();
}
}
@@ -0,0 +1,43 @@
package stirling.software.proprietary.failure;
import java.util.List;
/**
* Wire shape of one registry entry, served so a client can describe kinds it was not built with.
* Also a feature probe: a build without the proprietary module has no such route, so a 404 means
* there is no failure tracking here.
*/
public record FailureKindView(
String id,
FailureStage stage,
FailureSeverity severity,
FailureRemedy remedy,
FailureScope scope,
List<String> errorCodes,
String titleKey,
String descriptionKey,
String defaultTitle,
List<ActionDeclaration> actions) {
public static FailureKindView of(FailureKind kind) {
return new FailureKindView(
kind.getId(),
kind.getStage(),
kind.getSeverity(),
kind.getRemedy(),
kind.getScope(),
kind.getErrorCodes(),
kind.getTitleKey(),
kind.getDescriptionKey(),
kind.getDefaultTitle(),
kind.getActions().stream()
.map(
action ->
new ActionDeclaration(
action.name(), kind.labelKeyFor(action)))
.toList());
}
/** An action this kind offers, with the label key to render it by. */
public record ActionDeclaration(String id, String labelKey) {}
}

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