Compare commits

..

103 Commits

Author SHA1 Message Date
Florian Liebe
09a7af478f Release Zammad 7.2.1. 🚀 2026-10-06 07:16:53 +02:00
Florian Liebe
60b19ea12b Follow-up 09ca564bfcc72a9d83851ede09621d8b16c62b55 - Maintenance: Stop exposing the session ID in the signshow payload and authenticate the legacy WebSocket login from the session cookie.
Approved-by: Dominik Klein <dk@zammad.com>
2026-10-05 21:41:08 +02:00
Mantas Masalskis
8f6a50dcdc Maintenance: Do not establish a session when verifying a signup email address.
Approved-by: Dusan Vuckovic <dv@zammad.com>

Co-authored-by: Mantas <mantas@idev.lt>
2026-10-05 21:41:07 +02:00
Florian Liebe
8be3701cf7 Maintenance: Desktop view - Improved rendering of tree select option labels.
Approved-by: Dusan Vuckovic <dv@zammad.com>
2026-10-05 21:40:50 +02:00
Rene Reimann
e9ec16767e Maintenance: Desktop view - Improved rendering of select option labels
Approved-by: Florian Liebe <fl@zammad.com>

Co-authored-by: Florian Liebe <fl@zammad.com>
2026-10-05 21:40:50 +02:00
Florian Liebe
7092edc832 Tidy BREAKING_CHANGES.md file. 2026-10-05 21:40:49 +02:00
Florian Liebe
315d6ee72d Maintenance: Improve the handling of password reset and verification emails.
Approved-by: Dusan Vuckovic <dv@zammad.com>
2026-10-05 21:40:48 +02:00
Mantas Masalskis
6f5e7b368b Maintenance: Improve authorization scoping for the getting started wizard payload
Approved-by: Florian Liebe <fl@zammad.com>

Co-authored-by: Mantas <mantas@idev.lt>
2026-10-05 21:40:48 +02:00
Dominik Klein
3cdbaaa49a Maintenance: Improve notification template handling for some special situations.
Approved-by: Florian Liebe <fl@zammad.com>
2026-10-05 21:40:47 +02:00
Dusan Vuckovic
a44f475484 Maintenance: Stop exposing the session ID in the signshow payload and authenticate the legacy WebSocket login from the session cookie. 2026-10-05 21:40:46 +02:00
Mantas Masalskis
c2285d2f5a Maintenance: Render pushed collections for the recipient's assets level
Approved-by: Dusan Vuckovic <dv@zammad.com>

Co-authored-by: Mantas <mantas@idev.lt>
2026-10-05 21:40:45 +02:00
Benjamin Scharf
22a011c9b0 Maintenance: Missing authorization of the queried object in LinksController#index
Approved-by: Mantas Masalskis <mm@zammad.com>
2026-10-05 21:40:45 +02:00
Mantas Masalskis
52f49a81c4 Maintenance: Give the knowledge base controller policies their own permission declarations.
Approved-by: Rene Reimann <rr@zammad.com>

Co-authored-by: Mantas <mantas@idev.lt>
2026-10-05 21:40:44 +02:00
Florian Liebe
e2eca70847 Maintenance: Improved authorization of activity stream entries.
Approved-by: Mantas Masalskis <mm@zammad.com>
2026-10-05 21:40:43 +02:00
Florian Liebe
be9cb446e7 Maintenance: Improve Organization association attribute handling.
Approved-by: Mantas Masalskis <mm@zammad.com>
2026-10-05 21:40:42 +02:00
Florian Liebe
192e748b56 Maintenance: Improved handling of online notifications and related assets.
Approved-by: Mantas Masalskis <mm@zammad.com>
2026-10-05 21:40:42 +02:00
Florian Liebe
bfa1ba2678 Maintenance: Restrict attribute assignment via GraphQL object attribute values.
Approved-by: Dominik Klein <dk@zammad.com>
2026-10-05 21:40:41 +02:00
Mantas Masalskis
1947e0e57e Maintenance: Inactive sessions remain authorized over ActionCable GraphQL
Approved-by: Florian Liebe <fl@zammad.com>

Co-authored-by: Mantas <mantas@idev.lt>
2026-10-05 21:40:40 +02:00
Mantas Masalskis
987a23a0eb Maintenance: Improve consistency of role based knowledge base access checks.
Approved-by: Dusan Vuckovic <dv@zammad.com>
2026-10-05 21:40:39 +02:00
Florian Liebe
d86164e753 Maintenance: Improve handling of templates in desktop view.
Approved-by: Dominik Klein <dk@zammad.com>
2026-10-05 21:40:39 +02:00
Florian Liebe
131da801ca Maintenance: Improved handling of article preferences and attachments.
Approved-by: Dusan Vuckovic <dv@zammad.com>
2026-10-05 21:40:38 +02:00
Rolf Schmidt
466aad97da Maintenance: Improve consistency of role based group access checks.
Approved-by: Florian Liebe <fl@zammad.com>
2026-10-05 21:40:37 +02:00
Rolf Schmidt
0c695b2aea Maintenance: Harden overview sorting order direction handling.
Approved-by: Florian Liebe <fl@zammad.com>
2026-10-05 21:40:37 +02:00
Silke Jaenichen
28800968c1 Maintenance: Improve the sender format handling in quoted email headers.
Approved-by: Florian Liebe <fl@zammad.com>
2026-10-05 21:40:36 +02:00
Rene Reimann
b0c18f73e8 Maintenance: Improve ticket article create endpoint.
Approved-by: Florian Liebe <fl@zammad.com>
2026-10-05 21:40:35 +02:00
Dominik Klein
bf4234c45d Maintenance: Ticket update responses return unredacted user and organization assets to customers.
Approved-by: Rolf Schmidt <rolf.schmidt@zammad.com>
Approved-by: Florian Liebe <fl@zammad.com>
2026-10-05 21:40:35 +02:00
Silke Jaenichen
54ec0bd660 Maintenance: Align legacy knowledge base reader video marker rendering with the server-side helper.
Approved-by: Florian Liebe <fl@zammad.com>
2026-10-05 21:40:34 +02:00
Florian Liebe
b70fe5495c Maintenance: Update translations. 2026-10-05 21:40:33 +02:00
Dominik Klein
7af066e14c Maintenance: Improve authorization scoping for knowledge base reordering and publishing actions.
Approved-by: Florian Liebe <fl@zammad.com>
2026-10-05 21:40:32 +02:00
Silke Jaenichen
3e89f5a708 Maintenance: Desktop view - Escape display names in copy-to-clipboard HTML
Approved-by: Florian Liebe <fl@zammad.com>
2026-10-05 21:40:32 +02:00
Dominik Klein
64864fe299 Maintenance: Improve audit log facebook channel handling.
Co-authored-by: Florian Liebe <fl@zammad.com>
2026-10-05 21:40:31 +02:00
Florian Liebe
6ae4ac704c Update version. 2026-10-05 21:40:30 +02:00
Florian Liebe
6ee6ed38a1 Follow-up 6d3d2456ea - Maintenance: No password_change notification when password is set via the user-update endpoint. 2026-10-05 21:23:43 +02:00
Mantas Masalskis
057c30875b Fixes #6396 - Reply actions throw "Cannot read properties of null (reading... 2026-10-05 17:39:17 +03:00
Benjamin Scharf
8e542da2e9 Maintenance: Desktop view - Compact header ignores clicks on detail views too short to scroll the full header away 2026-10-05 14:44:45 +02:00
Dominik Klein
ce022acf1d Fixes #6404 - Postmaster filter list in admin UI silently shows only the first 500 filters. 2026-10-05 12:19:28 +02:00
Weblate
34d8267528 Maintenance: Update translated files and catalog 2026-10-05 07:02:21 +01:00
Weblate
7fe5bc2176 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Anntoo <jdoomm@gmail.com>
Co-authored-by: Balázs Úr <balazs@urbalazs.hu>
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Co-authored-by: Fredrik Norling <fredrik@xpagedeveloper.com>
Co-authored-by: Igor Costa <igor_sfx@yahoo.com.br>
Co-authored-by: Koen <k.pieters@veere.nl>
Co-authored-by: Martin Gruner <mg@zammad.com>
Co-authored-by: Ralf Schmid <rsc@zammad.com>
Co-authored-by: Thomas Meijer <tvm@bizconnect.dk>
Co-authored-by: WKobes <github@wouterkobes.nl>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: albanobattistella <albanobattistella@gmail.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/da/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/de/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/hu/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/it/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/nl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pt_BR/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sr_Cyrl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sv/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/uk/
Translation: Zammad/Zammad (stable version)
2026-10-05 05:58:31 +00:00
Benjamin Scharf
1a60c21ed9 Maintenance: Desktop view - Knowledge base floating toolbar is not clickable 2026-10-05 07:54:15 +02:00
Marcel Bialas
afc0fa3d32 Maintenance: Update role requirements for editing users.
Approved-by: Rene Reimann <rr@zammad.com>
2026-10-02 09:18:58 +02:00
Florian Liebe
31b3d8d6a3 Fixes #6411 - Adding underlines to the overview name causes parts of the string to be underlined in dashboard panel Overview. 2026-09-30 22:26:49 +02:00
Rolf Schmidt
15ac53f395 Fixes #6412 - Package precompile hangs on hidden pnpm modules directory purge confirmation. 2026-09-30 13:47:13 +02:00
Florian Liebe
4f51f6e0b2 Fixes #6407 - NoMethodError in HtmlSanitizer when an img style attribute contains an empty value (e.g. style="width:;"). 2026-09-30 11:58:33 +02:00
Rolf Schmidt
c7990360dc Fixes #6402 - XOAUTH2 token refresh floods the audit log with channel update entries. 2026-09-30 08:06:31 +02:00
Martin Gruner
25a52f084b Maintenance: Scan only the new commits for secrets in CI and limit the runtime of the lint jobs.
Approved-by: Florian Liebe <fl@zammad.com>


(cherry picked from commit d57b643e08)

1dac59ce Maintenance: Scan only the new commits for secrets in CI and limit the runtime of the lint jobs.
8e1d0548 Add comment
7cba00d7 simplify
daa47d2f Fail the secret detection job on gitleaks errors and fetch the history it needs.
3f890b0e Clarify the history comment of the secret detection job.
aa95c33a Match only the log level when checking for gitleaks errors.

Co-authored-by: Martin Gruner <mg@zammad.com>
2026-09-29 17:39:35 +02:00
Marcel Bialas
6f272133c7 Fixes #5175 - Resetting notifications settings does not restore default values for limit groups and sounds
Approved-by: Dusan Vuckovic <dv@zammad.com>
2026-09-29 15:22:34 +02:00
Dusan Vuckovic
4003050400 Fixes #6401 - Mobile view - Error pages show a broken image instead of the illustration. 2026-09-29 12:31:52 +01:00
Florian Liebe
d75d546040 Maintenance: Make the OCR integration spec tolerant of varying model output. 2026-09-29 12:09:48 +02:00
Florian Liebe
1a71ad2e02 Maintenance: Skip the Nominatim geolocation spec when VCR cassettes are disabled, as Nominatim blocks the CI runners. 2026-09-29 11:07:19 +02:00
Florian Liebe
78203c8985 Maintenance: Accept the address Clearbit currently returns in the Clearbit integration spec. 2026-09-29 09:53:15 +02:00
Florian Liebe
9789badfb8 Maintenance: Detailed search overwrites input typed right after a key release. 2026-09-28 21:58:47 +02:00
Dusan Vuckovic
2dd60acb79 Fixes #6377 - WhatsApp/Telegram loses linebreaks when replying to customer-initiated messages. 2026-09-28 16:53:06 +01:00
Florian Liebe
893778f260 Maintenance: Fix packager.io CI tests for Debian 11 after the LTS end.
Approved-by: Martin Gruner <mg@zammad.com>
2026-09-28 10:19:22 +02:00
Florian Liebe
4522facdf0 Maintenance: Update clearbit spec. 2026-09-28 09:11:01 +02:00
Florian Liebe
9a9084d817 Maintenance: Update dalli to 5.1.1, rack-proxy to 1.0.3.
Approved-by: Florian Liebe <fl@zammad.com>
2026-09-28 08:12:11 +02:00
Benjamin Scharf
154eb34c45 Maintenance: Desktop view - Browsing the knowledge base shows short interim states while loading 2026-09-25 13:40:51 +02:00
Benjamin Scharf
c2cff24650 Fixes #6378 - AI result feedback can be submitted again 2026-09-25 08:24:20 +02:00
Rene Reimann
744cee5b7f Fixes #6376 - Signed mail is displayed in Zammad with no visible content 2026-09-24 12:21:48 +02:00
Dusan Vuckovic
dcc41d255f Fixes #6379 - Monitoring reports old AI provider connection errors after the connection is no longer assigned. 2026-09-23 15:48:32 +01:00
Florian Liebe
313df7b4af Maintenance: Fix the Vitest command in the agent instructions. 2026-09-23 15:26:24 +02:00
Florian Liebe
321320be5e Maintenance: Disable playwright in CI.
Approved-by: Dominik Klein <dk@zammad.com>
2026-09-23 14:22:11 +02:00
Florian Liebe
9a69c6b85b Release Zammad 7.2.0. 🚀 2026-09-23 07:04:19 +02:00
Mantas Masalskis
a0403dfc9f Maintenance: Desktop view - Cannot create API token. 2026-09-22 23:22:58 +03:00
Rolf Schmidt
fc5b811f11 Maintenance: Migrating an existing multiselect attribute raises an ArgumentError.
(cherry picked from commit 9ff306fced)

fc3b23cd Maintenance: Migrating an existing multiselect attribute raises an ArgumentError.

Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
2026-09-22 15:38:33 +02:00
Dusan Vuckovic
44a5dd764f Follow-up b3075fe7d5 - Maintenance: Improve readability of HTTP log entries for Webhooks and AI providers. 2026-09-22 13:42:53 +01:00
Marcel Bialas
30e0b14ddf Maintenance: No password_change notification when password is set via the user-update endpoint 2026-09-22 11:32:51 +02:00
Marcel Bialas
cf65e3b09b Fixes #6369 - Knowledge Base wildcard search shows escaped special characters in titles. 2026-09-22 09:52:33 +02:00
Weblate
9d2c6cf523 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Anael Mobilia <contrib@anael.eu>
Co-authored-by: Anntoo <jdoomm@gmail.com>
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Co-authored-by: Igor Costa <igor_sfx@yahoo.com.br>
Co-authored-by: Ralf Schmid <rsc@zammad.com>
Co-authored-by: Thomas Meijer <tvm@bizconnect.dk>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/da/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/de/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pt_BR/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sr_Cyrl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/uk/
Translation: Zammad/Zammad (pre-release version)
2026-09-22 05:37:06 +00:00
Martin Gruner
280693014a Follow-up 670a1e13fe - Fixes #6310 - Audit log: untranslated values in object type column
Approved-by: Martin Gruner <mg@zammad.com>


(cherry picked from commit 76a2bec1f0)

93db90de Follow-up 670a1e13fe - Fixes #6310 - Audit log:...

Co-authored-by: Mantas Masalskis <mm@zammad.com>
2026-09-22 07:36:29 +02:00
Dusan Vuckovic
5bd572d2ac Fixes #6370 - Core workflow ignores default value if select attribute options are mutated. 2026-09-21 15:56:21 +01:00
Mantas Masalskis
fac22c1cf6 Fixes #6363 - Desktop view: a date or datetime attribute marks every ticket tab as changed. 2026-09-21 16:08:08 +03:00
Benjamin Scharf
7689d03b89 Fixes #6372 - Ticket draft gets duplicated everytime the Browser back button is used. 2026-09-21 11:37:57 +02:00
Rolf Schmidt
e14dae3f7b Maintenance: Cover the staged package check in the pending migrations initializer for Kubernetes deployments.
Approved-by: Martin Gruner <mg@zammad.com>


(cherry picked from commit c545ff10b2)

7cbb9c79 Maintenance: Cover the staged package check in the pending migrations...

Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
2026-09-18 15:14:54 +02:00
Rolf Schmidt
54f25639d3 Maintenance: Wait for staged addon packages before serving in container environments.
Approved-by: Dusan Vuckovic <dv@zammad.com>


(cherry picked from commit 301f2b11e1)

48185e1a Maintenance: Wait for staged addon packages before serving in container environments.
cea7c3ca Maintenance: Mark the pending package messages as translatable.
cee39cb6 Maintenance: Use guard clauses and untranslated messages for the staged package check.
c0a75ba1 Maintenance: Use guard clauses with extracted predicates for the staged package checks.

Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
2026-09-18 13:05:32 +02:00
Rolf Schmidt
6d2da5843f Maintenance: Core Workflow - Do not show attributes without matching screen permission options.
Approved-by: Florian Liebe <fl@zammad.com>


(cherry picked from commit 847138afaa)

d95d143a Maintenance: Core workflow - Do not show attributes without matching screen permission options.
c36b978f Maintenance: Core Workflow - Only remove attributes without matching...
a234ea33 Maintenance: Object Manager - Compare the ticket customer by id when resolving element screens.
c9d21f25 Maintenance: Policies - Compare the record user by id to support user context objects.
bafb76bf Maintenance: UserContext - Support ActiveRecord equality checks against the wrapped user.
6fe130cf Maintenance: Restore object comparisons for the current user now that...
ec0a304c Maintenance: Core Workflow - Add condition spec for session group ids with a user context.
7858d775 Maintenance: Core Workflow - Pin the visibility of screens that resolve to no options.

Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
2026-09-18 12:05:43 +02:00
Rolf Schmidt
c495d84328 Feature: Show a read-only package overview in Docker environments.
Approved-by: Rolf Schmidt <rolf.schmidt@zammad.com>


(cherry picked from commit 925bea2505)

69448a70 Feature: Show a read-only package overview in Docker environments.

Co-authored-by: Dusan Vuckovic <dv@zammad.com>
2026-09-18 11:59:57 +02:00
Rolf Schmidt
b455ec4d37 Follow-up f6114a6aca - Maintenance: Allow building container images with preinstalled addon packages.
Approved-by: Martin Gruner <mg@zammad.com>


(cherry picked from commit 5124de7d3e)

42f69fb6 Follow-up f6114a6aca - Maintenance: Allow...

Co-authored-by: Dusan Vuckovic <dv@zammad.com>
2026-09-18 11:57:46 +02:00
Weblate
f21960b4d6 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Balázs Úr <balazs@urbalazs.hu>
Co-authored-by: Igor Costa <igor_sfx@yahoo.com.br>
Co-authored-by: Marcel Bialas <mtzl.29@proton.me>
Co-authored-by: Martin Gruner <mg@zammad.com>
Co-authored-by: Ralf Schmid <rsc@zammad.com>
Co-authored-by: Thomas Meijer <tvm@bizconnect.dk>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/da/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/de/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/hu/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pt_BR/
Translation: Zammad/Zammad (pre-release version)
2026-09-17 22:12:38 +00:00
Dusan Vuckovic
98a6bcc601 Follow-up for 5f01c16a75 - Feature: Desktop view - Add/Edit/Remove a Knowledge Base Category. 2026-09-17 16:02:43 +01:00
Martin Edenhofer
da8a8dd958 Maintenance: Fix publiccode.yml version, languages and links.
Co-authored-by: Martin Gruner <mg@zammad.com>
Co-authored-by: Martin Edenhofer <me@edenhofer.de>
2026-09-17 15:05:28 +02:00
Benjamin Scharf
a4c23643eb Fixes #6367 - Knowledge Base next/previous buttons use original language. 2026-09-17 14:44:54 +02:00
Florian Liebe
886134ceb5 Maintenance: Resolve mocked GraphQL union fields by their declared type. 2026-09-17 12:55:56 +02:00
Mantas Masalskis
3f063a7014 Fixes #6340 - Mail body silently falls back to CharDet when Encoding.find... 2026-09-17 10:02:59 +03:00
Weblate
1976b06c01 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Anntoo <jdoomm@gmail.com>
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sr_Cyrl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/uk/
Translation: Zammad/Zammad (pre-release version)
2026-09-16 06:57:36 +00:00
Weblate
25623e2928 Maintenance: Update translated files and catalog 2026-09-15 23:21:25 +01:00
Weblate
556937b6de Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Anael Mobilia <contrib@anael.eu>
Co-authored-by: Anntoo <jdoomm@gmail.com>
Co-authored-by: Balázs Úr <balazs@urbalazs.hu>
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Co-authored-by: Igor Costa <igor_sfx@yahoo.com.br>
Co-authored-by: Murat Duman <mduman@metu.edu.tr>
Co-authored-by: Ralf Schmid <rsc@zammad.com>
Co-authored-by: Random <random_r@users.noreply.translations.zammad.org>
Co-authored-by: Thomas Meijer <tvm@bizconnect.dk>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: albanobattistella <albanobattistella@gmail.com>
Co-authored-by: k-fujimaru <k.fujimaru@natsuhara.link>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/da/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/de/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/hu/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/it/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ja/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pt_BR/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sr_Cyrl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/tr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/uk/
Translation: Zammad/Zammad (pre-release version)
2026-09-15 22:12:53 +00:00
Martin Gruner
8a7a138f4e Maintenance: Restore $VERBOSE after em-websocket monkey patches.
Approved-by: Florian Liebe <fl@zammad.com>


(cherry picked from commit 858e6fcf04)

6f082a48 Maintenance: Restore $VERBOSE after em-websocket monkey patches.
94e142cb Maintenance: Restore $VERBOSE in an ensure, via silence_warnings.
599e5526 Maintenance: Revert to the plain module nesting under silence_warnings.
e086995d Revert previous commit, simplify solution

Co-authored-by: Martin Gruner <mg@zammad.com>
2026-09-15 12:11:26 +02:00
Rolf Schmidt
252fa26d6f Maintenance: Load the models before generating the GraphQL introspection to include dynamic schema parts.
Approved-by: Dusan Vuckovic <dv@zammad.com>


(cherry picked from commit e8902e14f1)

9e9db3bc Maintenance: Load the models before generating the GraphQL introspection to...

Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
2026-09-15 10:34:23 +02:00
Rolf Schmidt
893ac7943e Maintenance: Allow building container images with preinstalled addon packages.
Approved-by: Martin Gruner <mg@zammad.com>

Co-authored-by: Martin Gruner <mg@zammad.com>
Co-authored-by: Dusan Vuckovic <dv@zammad.com>


(cherry picked from commit f6114a6aca)

284237a6 add proof of concept for installing addon packages into container images
2efa76d1 install additional gems from addon packages during image build
2de39cdc refuse mutating package operations via rest api and cli in container environments
65179f86 Split the docker test job into a template and invocation
3ce5ca96 Fix docker ci script
e234bd0c remove auto install mechanism and register addon packages without file system...
5af40644 install addon packages in dependency order during container init
1cf34595 use pluck in addon package install script
96e58bdd add install_dir rake task to install addon packages from a directory in dependency order
d39deace Fix docker CI job and add one for addon testing
12cbdf60 Simplify Dockerfile to only generate one layer, try to fix CI
512a1785 Try to fix CI
840c22df Auto-start docker jobs during this MR
2036ac5b Fix docker test
6cd05602 document that the setting types catch-all is required for addon settings in container images
cf3b13f3 improve wording of setting types catch-all comment
1222960c simplify setting types catch-all comment
b2d9554b Read addon packages to unpack from packages/install/ during image build,...
3975ee12 Uninstall addon packages staged in packages/uninstall/ during container init.
436ed5aa fix: Unpack all files when uninstalling packages (incl. frontend).
c256e7fe WIP testing
293f7cac Fix file mode
f34ee186 Simplify docker job, don't require ruby
12ae1f4d Fix file path
202211d2 Wait for zammad-init to complete
90b84f7a Add debug output
41e27c94 Fix migration class name
489561d1 add optional build label to the docker version string
f56cf32d Drop file added by accident
e503ce81 Try to solve migration issue
570fefb3 test: Merge two cases together so the package can be uninstalled correctly.
b215f079 yolo: oops
967c5453 Simplify docker test job
da9fe3d9 WIP, drop docker push
a5011236 Fix test invocation
c034f6b8 WIP
b6e247ac Try readonly fs for CI
00de4360 Add tmpfs also for /tmp
9d58ed71 Try to fix tests in readonly fs
13b40e0e Drop readonly part again, we'll address this later on in the docker stack
de7c86bb Also test package dependencies
731af123 Fix setting names
d9e5cddb Typo
913c3492 Centralized container env detection
0ec84a8c doc: Drop superfluous page in dev manual.
0e666fb0 Make docker jobs manual again
2a41f6eb Maintenance: allow package upgrades of packages with dependents and remove spec file leftovers.

Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
2026-09-15 10:34:06 +02:00
Dusan Vuckovic
c7623e7c68 Fixes #6355 - SLA times longer than 41 days cannot be edited via GUI. 2026-09-15 08:25:49 +01:00
Florian Liebe
a3a781ae36 Maintenance: Repair spec assertions that never ran or depend on the driver.
Approved-by: Florian Liebe <fl@zammad.com>

Co-authored-by: Dominik Klein <dk@zammad.com>
2026-09-14 12:40:02 +02:00
Philipp Reger
6f41176e43 Fixes #6357 - S/MIME decryption fails for forwarded emails when recipient is only present in Delivered-To. 2026-09-14 10:14:39 +01:00
Florian Liebe
1a9661dfc1 Maintenance: Fix api_client:php job by disabling PHPUnit coverage. 2026-09-11 22:25:56 +02:00
Benjamin Scharf
e0deaf1780 Fixes #6352 - Checked parameter ignored on POST checklist_items 2026-09-10 12:45:33 +02:00
Benjamin Scharf
94a6f01d75 Maintenance: Desktop view - Detail search triggers network requests on every keystroke
Approved-by: Marcel Bialas <mb@zammad.com>
2026-09-09 10:46:06 +01:00
Marcel Bialas
8bbd65fbb7 Feature: Desktop view - Add a knowledge base answers tab to the global detailed search
Approved-by: Benjamin Scharf <bs@zammad.com>
2026-09-09 10:45:50 +01:00
Benjamin Scharf
c16f6c57b9 Feature: Desktop view - Limit quick search results to 5 per entity type when...
Approved-by: Marcel Bialas <mb@zammad.com>
2026-09-09 10:45:40 +01:00
Benjamin Scharf
7c11c7bb5e Maintenance: Remove truncation logging from KnowledgeBase search
Approved-by: Mantas Masalskis <mm@zammad.com>
2026-09-09 10:45:25 +01:00
Benjamin Scharf
c6f201a923 Feature: Desktop view - Show knowledge base answers in quicksearch results
Approved-by: Mantas Masalskis <mm@zammad.com>
2026-09-09 10:45:09 +01:00
Dusan Vuckovic
17c7b30ef6 Maintenance: Show warning alert when editing Zammad AI provider in SaaS environment. 2026-09-08 15:52:39 +01:00
Dusan Vuckovic
876ea9ced5 Fixes #6305 - CSRF token verification failure during login after updating to Zammad 7.1 2026-09-04 15:07:39 +01:00
Dusan Vuckovic
e45d33852a Follow-up 61e0da447f - Feature: Desktop view - Follow-ups from the knowledge base answer review. 2026-09-04 11:03:45 +01:00
Florian Liebe
b20c13b78f Update version information for Zammad 7.2.0. 2026-09-03 22:55:58 +02:00
1176 changed files with 26779 additions and 106962 deletions

View File

@@ -1,2 +1,2 @@
https://github.com/heroku/heroku-buildpack-nodejs.git#v371
https://github.com/heroku/heroku-buildpack-nodejs.git#v365
https://github.com/pkgr/heroku-buildpack-ruby.git#v361-1

View File

@@ -78,8 +78,8 @@ make a review look thorough is not.
Also out of scope in every review:
- Style and formatting a linter or formatter owns (RuboCop, ESLint, oxlint, Oxfmt, Stylelint),
unless it actively obscures meaning or breaks a documented standard.
- Style and formatting a linter owns (RuboCop, ESLint, Prettier), unless it actively obscures
meaning or breaks a documented standard.
- Naming preferences not covered by a convention doc.
- Speculative future requirements that are not in the issue.
- Pre-existing code visible in diff context but untouched by the change.
@@ -308,19 +308,6 @@ or translation quality for some users.
other `$t(...)` / `i18n.t(...)` calls or the base locale file. If a near-identical string
exists, flag the new one as a duplicate and suggest reusing it — every distinct string adds
translator workload across all supported languages.
- **Danger styling and confirmations** — `danger` and `remove` (which counts as danger) mark the
click that cannot be undone, so they stay only on controls that act immediately. Flag a `danger`
or `remove` trigger whose handler ends in `waitForConfirmation` / `waitForVariantConfirmation`,
or opens a flyout that asks first. What replaces it depends on the kind of trigger: a `danger`
button becomes `tertiary` with an icon naming the action (`trash3` for delete and discard,
`arrow-counterclockwise` for reset); a menu item carries no variant at all, icon included; a
`remove` icon button loses the variant, keeps its icon, and takes the variant UX decides per
place (`tertiary` so far), so do not assume one. Flag a confirmation for irrecoverable loss
(deleting records, resetting user settings, discarding unsaved changes, clearing filters or
recent lists) whose confirm button is not `danger`: the `delete` and `unsaved` variants of
`CommonConfirmationDialog` already render one, so only the default `confirm` variant needs
`buttonVariant: 'danger'`. A control that asks only in some states (a dirty taskbar tab) follows
the state.
- Never flag a change to `i18n/*.po` as the fix. Those files are managed externally.
### Backend — `[backend]`

View File

@@ -9,7 +9,7 @@ Resolvers are thin wrappers. Business logic lives in `app/services/service/`:
```ruby
def resolve(...)
Service::SomeAction.execute(...)
Service::SomeAction.new(...).execute
end
```

View File

@@ -64,13 +64,12 @@ Do not "fix" these without a decision:
inactive knowledge base that way — the policy for anyone with effective access
to the record (a reader included), the scope for the `knowledge_base.editor`
permission.
- `Gql::Concerns::SearchesKnowledgeBaseAnswers` answers with an **empty result**
instead of raising when no knowledge base is active. It is reached from a
document that asks for more than the knowledge base — the desktop quicksearch
requests every searchable entity in one query — and the search field is
`null: false`, so a raise would null the root and take the ticket, user and
organization groups down with it on every instance that has no knowledge base.
The knowledge base's own queries, `Gql::Queries::KnowledgeBase::Search`
included, do raise: they are asked for on their own.
- `Gql::Concerns::SearchesKnowledgeBaseAnswers` and
`Gql::Queries::KnowledgeBase::Search` answer with an **empty result** instead of
raising when no knowledge base is active. Both are reached from a document that
asks for more than the knowledge base — the desktop quicksearch requests every
searchable entity in one query — and the search field is `null: false`, so a
raise would null the root and take the ticket, user and organization groups down
with it on every instance that has no knowledge base.
Each of these carries the reasoning in a code comment; keep the two in sync.

View File

@@ -36,8 +36,8 @@ is not enough.
### Skeleton
Minimal concern following `app/models/mention/assets.rb` (belongs_to user +
polymorphic relation):
Minimal concern following `app/models/mention/assets.rb` and
`app/models/audit_log/assets.rb` (belongs_to user + polymorphic relation):
```ruby
class MyModel
@@ -67,6 +67,7 @@ end
### Reference examples
- `app/models/mention/assets.rb` — user + polymorphic `mentionable`
- `app/models/audit_log/assets.rb` — optional user + polymorphic `auditable`
- `app/models/online_notification/assets.rb` — lookup tables + created/updated by
- `app/models/group/assets.rb` — attribute filtering and per-user authorization

View File

@@ -170,13 +170,13 @@ existing code.
- **UI-6 (proposed)** — Should new UI be required to compose existing design-system components and
tokens, with a new primitive treated as a decision rather than a detail?
- **UI-7 (proposed)** — Should a resumable view offer its own taskbar-behaviour-on-update setting,
stored independently per view? Seen once, in the story that introduced editing knowledge base
answers; unclear whether it generalises.
stored independently per view? Seen once, in
`zammad/coordination-desktop-view#785`; unclear whether it generalises.
- **When is a new setting justified?** `DEFAULT-2` states the preference but not the test. There are
~300 settings today, so a usable criterion would have real leverage.
- **Are bulk or batch variants expected** for single-record actions, or added only on demand?
- **Where does deferred or scheduled state belong** in the interface? The same story grew a whole
scheduled visibility capability, which suggests there is no settled pattern for it yet.
- **Where does deferred or scheduled state belong** in the interface? `#785` grew a whole scheduled
visibility capability, which suggests there is no settled pattern for it yet.
- **How much of the legacy interface does a new feature have to reach**, and when is parity
explicitly not required?
- **How custom file overrides and packages are meant to work in a container**, given `DEPLOY-1`.

View File

@@ -146,7 +146,7 @@ A **layer slice** can never be a story. "Add the service and the mutation" has n
and nothing observable — forcing it into story form produces a fake user story, which is worse than
a large one. Layer slices are tasks. Split stories along capability and surface seams only.
### Worked example — editing knowledge base answers
### Worked example — `zammad/coordination-desktop-view#785`
"Edit an answer of the knowledge base", one story, sixteen tasks, one very large merge request.
Its criteria classified by the test above:

View File

@@ -40,19 +40,6 @@ Selenium opens a real, visible browser window.
SELENIUM_BROWSER_HEADLESS=1 RAILS_ENV=test VITE_TEST_MODE=1 bundle exec rspec spec/system/path/to/file_spec.rb
```
### Ports of a test run
The suite serves Capybara on `CAPYBARA_PORT` (default `3001`) and its websocket
server on `WS_PORT` (default `6043`) — next to, never on, the ports of the
development stack, so a run does not collide with a stack of the same checkout.
Set both to run two suites side by side; they still share the `test` database,
the Redis keys and the Elasticsearch indices, so those (`DATABASE_URL`,
`REDIS_URL`, `ES_INDEX`) need separating as well.
```bash
CAPYBARA_PORT=3101 WS_PORT=6143 bundle exec rspec spec/system/path/to/file_spec.rb
```
### Capybara form helpers (new frontend stack)
System tests for the Vue frontend use custom helpers to interact

View File

@@ -40,7 +40,7 @@ finding, what is out of scope, how to classify findings, and how to write them.
Apply it rather than your own judgement about what matters, and do not restate it
here.
Three things from it deserve emphasis in this position specifically:
Two things from it deserve emphasis in this position specifically:
- **The flagging threshold.** You may be reviewing work in progress, where scaffolding
and half-finished edges are expected. Report what meets the bar and nothing else.

View File

@@ -39,26 +39,6 @@ The CoffeeScript frontend uses REST controllers.
- Never edit translation files (`i18n/*.po`) directly —
translations are managed via translations.zammad.org.
### Code Comments
Comment only what the code cannot say itself: a non-obvious _why_, a
workaround with its reason, a spec/protocol quirk, or a deliberate
deviation from a pattern. Otherwise add no comment — clear naming and
small methods are preferred over explanation.
- One or two short lines, in the style of the surrounding file.
- Never restate what the next line does, summarise a block, label
sections (`# --- setup ---`), or narrate a change (`# new`, `# was …`,
`# fixed`).
- Keep out reasoning that belongs in the commit message, the issue or
the MR description.
- Never reference an issue from an internal repository (`coordination-*`) —
this repository is publicly mirrored. Reference a public `zammad/zammad`
issue, or state the reason without a link.
- Never mark a fix for a security vulnerability as such — this repository
is publicly mirrored, and the comment points at the vulnerable code in
installations that are not updated yet.
### UI Principles
- Icon-only buttons need `v-tooltip` supplying the accessible name.
@@ -67,13 +47,6 @@ small methods are preferred over explanation.
- Translatable strings: use complete, meaningful source strings,
sentence case for English source strings, and check for existing
near-duplicate strings before adding new ones.
- Red marks the point of no return: `danger` and `remove` stay only on
controls that act immediately. A trigger that only opens a confirmation
dialog or a confirming flyout is not red: a button becomes `tertiary`
with an icon naming the action (`trash3` for delete and discard,
`arrow-counterclockwise` for reset), a menu item carries no variant, a
`remove` icon button keeps its icon and takes the variant UX names per
place. The decisive button in the dialog or flyout is `danger`.
## Essential Commands
@@ -95,32 +68,6 @@ pnpm generate-graphql-api # Regenerate GraphQL types aft
pnpm generate-setting-types # Regenerate Config types after setting changes
```
### Development stack
A browser-clickable instance for this checkout. Start it via `bin/dev` rather
than `forego` directly, and wait for it to answer before using it.
```bash
RAILS_ENV=development bundle exec rake db:create zammad:bootstrap:reset # once per checkout
bin/dev # does not exit; run it in the background
```
It serves on `localhost:3000`, with the websocket server on `6042` and Vite on
`3036`. Export `ZAMMAD_RAILS_PORT`, `ZAMMAD_WEBSOCKET_PORT` and
`VITE_RUBY_PORT` before `bin/dev` to move them — the whole stack has to agree
on them, so export, do not set them per process.
`zammad:bootstrap:reset` migrates, seeds and runs the auto wizard, which creates
`admin@example.com` / `test`. Plain `zammad:db:init` skips the wizard and leaves
the browser on the getting-started screen. Never run `zammad:bootstrap:init` or
`zammad:setup:db_config`: they overwrite `config/database.yml` and prompt on
stdin, which hangs a non-interactive session.
The test suite uses its own ports (`3001` and `6043`) and its own database, so
it can run while the stack is up. Several checkouts, however, share the
development database and Redis — only one stack per machine unless
`DATABASE_URL` and `REDIS_URL` say otherwise.
## Development Lifecycle
Issues are tracked on **GitHub**, code is developed on a self-hosted **GitLab**.
@@ -131,7 +78,7 @@ Stories are refined and broken down into task issues before this lifecycle
starts (`/refine-story` → `/plan-story`). Bugs skip that and enter it directly.
Lifecycle: Understand → Research → Plan → Branch (`/prepare-issue-branch`) →
Implement → Test → Review → Commit → MR (`/create-mr`) → Cherry-pick
Implement → Test → Commit → Review → MR (`/create-mr`) → Cherry-pick
(`/cherry-pick-to-stable`)
See `.dev/agent_docs/development_workflow.md` for the full lifecycle and
@@ -164,7 +111,7 @@ You MUST read the relevant file(s) below before responding when working on that
## CodeGraph
In repositories indexed by CodeGraph (a `.codegraph/` directory exists at the repo
root), reach for it before grep/find or reading files when you need to understand
root), reach for it BEFORE grep/find or reading files when you need to understand
or locate code:
- **MCP tool** (when available): `codegraph_explore` answers most code questions
@@ -173,9 +120,14 @@ or locate code:
in the query to read its current line-numbered source. If it's listed but
deferred, load it by name via tool search.
- **Shell** (always works): `pnpm exec codegraph explore "<symbol names or question>"`
prints the same output using the repository-pinned CodeGraph version.
prints the same output — `codegraph` is a local dev dependency, so the bare
command is not on `PATH`.
If there is no `.codegraph/` directory, skip CodeGraph entirely — indexing is the
user's decision. If they ask to initialize it, run `pnpm codegraph:init`. Developer
setup and removal are documented in
`doc/developer_manual/development_environment/codegraph-setup.md`.
Setup is per developer: `pnpm codegraph:install` indexes this checkout and registers
the server with the agent harnesses it finds (Claude Code, Cursor, Codex CLI,
opencode, …). Say yes when it offers to put the CLI on `PATH`: that is how they
launch it. `pnpm codegraph:uninstall` reverts it. Nothing lands in a commit —
`.codegraph/` and `.mcp.json` are gitignored, and the harness registration lives in
each tool's own config outside the repo.
If there is no `.codegraph/` directory, skip CodeGraph entirely — indexing is the user's decision.

View File

@@ -20,14 +20,7 @@ export default defineConfig({
downloadsFolder: '.dev/cypress/downloads',
screenshotsFolder: '.dev/cypress/screenshots',
videoCompression: false,
// Cypress 16 defaults to `0`, which types faster than the rich text editor can
// process input events.
keystrokeDelay: 10,
// Cypress 16 defaults to `modern`, which relies on `Element.checkVisibility()`
// and therefore reports elements moved out of the viewport via `transform` as
// visible.
visibilityStrategy: 'legacy',
expose: {
env: {
CY_CI: isCYCI,
pluginVisualRegressionDiffConfig: {
threshold: 0.02,

View File

@@ -2,10 +2,10 @@
"private": true,
"type": "module",
"devDependencies": {
"@frsource/cypress-plugin-visual-regression-diff": "^4.2.1",
"@frsource/cypress-plugin-visual-regression-diff": "^4.1.1",
"@testing-library/cypress": "^10.1.3",
"cypress": "^16.1.1",
"cypress-real-events": "^1.15.1"
"cypress": "^15.21.1",
"cypress-real-events": "^1.15.0"
},
"imports": {
"#tests/*": "../../app/frontend/tests/*",

View File

@@ -9,17 +9,17 @@ importers:
.:
devDependencies:
'@frsource/cypress-plugin-visual-regression-diff':
specifier: ^4.2.1
version: 4.2.1(cypress@16.1.1)
specifier: ^4.1.1
version: 4.1.1(cypress@15.21.1)
'@testing-library/cypress':
specifier: ^10.1.3
version: 10.1.3(cypress@16.1.1)
version: 10.1.3(cypress@15.21.1)
cypress:
specifier: ^16.1.1
version: 16.1.1
specifier: ^15.21.1
version: 15.21.1
cypress-real-events:
specifier: ^1.15.1
version: 1.15.1(cypress@16.1.1)
specifier: ^1.15.0
version: 1.15.0(cypress@15.21.1)
packages:
@@ -45,12 +45,12 @@ packages:
'@emnapi/runtime@1.11.3':
resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==}
'@frsource/base64@1.0.237':
resolution: {integrity: sha512-X0tSa8rrusTEkYNy7aE+M+sZlsdyjMt8OgxWc6RerYefph3EKIVRTaL0qg8iw53iabvM7mJDK8MNc4W1yTAz2w==}
'@frsource/base64@1.0.213':
resolution: {integrity: sha512-KaW0AbWO5gLtFJ+C9QZjY25P2dKkOFBtKtuyIj+DEA87moaO4cpovazCE8Zps8JP7cVyuZ+rDxkXlQTt8xli+Q==}
'@frsource/cypress-plugin-visual-regression-diff@4.2.1':
resolution: {integrity: sha512-zu+Lfet2mydw+kkGMQ96gN6w8uejPsonKgmaybAMrS3HVXx4V28D961Q8f7ohs9hGa+v+njPJeGbTZuKpnLCkQ==}
engines: {node: '>=20.9.0'}
'@frsource/cypress-plugin-visual-regression-diff@4.1.1':
resolution: {integrity: sha512-O/RAf8shfs9UcwzPfR0TmrXnuy0UymjPNrbLhXFsls2bHsnWH++LskqXgkMdi9fnTA7E51XKqptUexNKOeEg7g==}
engines: {node: '>=14'}
peerDependencies:
cypress: '>=13.0.0'
@@ -58,160 +58,160 @@ packages:
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
engines: {node: '>=18'}
'@img/sharp-darwin-arm64@0.35.4':
resolution: {integrity: sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==}
'@img/sharp-darwin-arm64@0.35.3':
resolution: {integrity: sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [darwin]
'@img/sharp-darwin-x64@0.35.4':
resolution: {integrity: sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==}
'@img/sharp-darwin-x64@0.35.3':
resolution: {integrity: sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [darwin]
'@img/sharp-freebsd-wasm32@0.35.4':
resolution: {integrity: sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==}
'@img/sharp-freebsd-wasm32@0.35.3':
resolution: {integrity: sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==}
engines: {node: '>=20.9.0'}
os: [freebsd]
'@img/sharp-libvips-darwin-arm64@1.3.3':
resolution: {integrity: sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==}
'@img/sharp-libvips-darwin-arm64@1.3.2':
resolution: {integrity: sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==}
cpu: [arm64]
os: [darwin]
'@img/sharp-libvips-darwin-x64@1.3.3':
resolution: {integrity: sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==}
'@img/sharp-libvips-darwin-x64@1.3.2':
resolution: {integrity: sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-linux-arm64@1.3.3':
resolution: {integrity: sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==}
'@img/sharp-libvips-linux-arm64@1.3.2':
resolution: {integrity: sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-arm@1.3.3':
resolution: {integrity: sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==}
'@img/sharp-libvips-linux-arm@1.3.2':
resolution: {integrity: sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-ppc64@1.3.3':
resolution: {integrity: sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==}
'@img/sharp-libvips-linux-ppc64@1.3.2':
resolution: {integrity: sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-riscv64@1.3.3':
resolution: {integrity: sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==}
'@img/sharp-libvips-linux-riscv64@1.3.2':
resolution: {integrity: sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-s390x@1.3.3':
resolution: {integrity: sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==}
'@img/sharp-libvips-linux-s390x@1.3.2':
resolution: {integrity: sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-x64@1.3.3':
resolution: {integrity: sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==}
'@img/sharp-libvips-linux-x64@1.3.2':
resolution: {integrity: sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linuxmusl-arm64@1.3.3':
resolution: {integrity: sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==}
'@img/sharp-libvips-linuxmusl-arm64@1.3.2':
resolution: {integrity: sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-libvips-linuxmusl-x64@1.3.3':
resolution: {integrity: sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==}
'@img/sharp-libvips-linuxmusl-x64@1.3.2':
resolution: {integrity: sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-linux-arm64@0.35.4':
resolution: {integrity: sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==}
'@img/sharp-linux-arm64@0.35.3':
resolution: {integrity: sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-arm@0.35.4':
resolution: {integrity: sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==}
'@img/sharp-linux-arm@0.35.3':
resolution: {integrity: sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==}
engines: {node: '>=20.9.0'}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-linux-ppc64@0.35.4':
resolution: {integrity: sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==}
'@img/sharp-linux-ppc64@0.35.3':
resolution: {integrity: sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==}
engines: {node: '>=20.9.0'}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-riscv64@0.35.4':
resolution: {integrity: sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==}
'@img/sharp-linux-riscv64@0.35.3':
resolution: {integrity: sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==}
engines: {node: '>=20.9.0'}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-s390x@0.35.4':
resolution: {integrity: sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==}
'@img/sharp-linux-s390x@0.35.3':
resolution: {integrity: sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==}
engines: {node: '>=20.9.0'}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-linux-x64@0.35.4':
resolution: {integrity: sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==}
'@img/sharp-linux-x64@0.35.3':
resolution: {integrity: sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-linuxmusl-arm64@0.35.4':
resolution: {integrity: sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==}
'@img/sharp-linuxmusl-arm64@0.35.3':
resolution: {integrity: sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-linuxmusl-x64@0.35.4':
resolution: {integrity: sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==}
'@img/sharp-linuxmusl-x64@0.35.3':
resolution: {integrity: sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-wasm32@0.35.4':
resolution: {integrity: sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==}
'@img/sharp-wasm32@0.35.3':
resolution: {integrity: sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==}
engines: {node: '>=20.9.0'}
'@img/sharp-webcontainers-wasm32@0.35.4':
resolution: {integrity: sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==}
'@img/sharp-webcontainers-wasm32@0.35.3':
resolution: {integrity: sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==}
engines: {node: '>=20.9.0'}
cpu: [wasm32]
'@img/sharp-win32-arm64@0.35.4':
resolution: {integrity: sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==}
'@img/sharp-win32-arm64@0.35.3':
resolution: {integrity: sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [win32]
'@img/sharp-win32-ia32@0.35.4':
resolution: {integrity: sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==}
'@img/sharp-win32-ia32@0.35.3':
resolution: {integrity: sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==}
engines: {node: ^20.9.0}
cpu: [ia32]
os: [win32]
'@img/sharp-win32-x64@0.35.4':
resolution: {integrity: sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==}
'@img/sharp-win32-x64@0.35.3':
resolution: {integrity: sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [win32]
@@ -386,22 +386,22 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
cypress-real-events@1.15.1:
resolution: {integrity: sha512-BiTyqYcNHiIFWfSLRn3H+TgTvpnTGOKfl4Brz51yeSGKMxBbD60wtRQw31nII00yQ67Xg3C18mU8lqUxDN3OUA==}
cypress-real-events@1.15.0:
resolution: {integrity: sha512-in98xxTnnM9Z7lZBvvVozm99PBT2eEOjXRG5LKWyYvQnj9mGWXMiPNpfw7e7WiraBFh7XlXIxnE9Cu5o+52kQQ==}
peerDependencies:
cypress: ^4.x || ^5.x || ^6.x || ^7.x || ^8.x || ^9.x || ^10.x || ^11.x || ^12.x || ^13.x || ^14.x || ^15.x || ^16.x
cypress: ^4.x || ^5.x || ^6.x || ^7.x || ^8.x || ^9.x || ^10.x || ^11.x || ^12.x || ^13.x || ^14.x || ^15.x
cypress@16.1.1:
resolution: {integrity: sha512-T09qNpMKssD6eEMqqfywQ8OUub/VrNCvIUFAyQCfWsJ6o4CZ5Yeapce+WoZY9FXly6+3+pk7sjEB5voxduWQlQ==}
engines: {node: ^22.0.0 || ^24.0.0 || >=26.0.0}
cypress@15.21.1:
resolution: {integrity: sha512-ogHpHMj0XNlZA5MGzjg8SWHf0eMw9lTwVQRKjpcT1GzNTxNUjwnHA8YCG6nOJ8ThU+XZaUxJgpMYZnJ//8aDaw==}
engines: {node: ^20.1.0 || ^22.0.0 || >=24.0.0}
hasBin: true
dashdash@1.14.1:
resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==}
engines: {node: '>=0.10'}
dayjs@1.11.23:
resolution: {integrity: sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==}
dayjs@1.11.21:
resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==}
debug@3.2.7:
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
@@ -490,6 +490,7 @@ packages:
extsprintf@1.3.0:
resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==}
engines: {'0': node >=0.6.0}
forever-agent@0.6.1:
resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==}
@@ -624,6 +625,7 @@ packages:
jsprim@2.0.2:
resolution: {integrity: sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==}
engines: {'0': node >=0.6.0}
listr2@9.0.5:
resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==}
@@ -792,8 +794,8 @@ packages:
engines: {node: '>=10'}
hasBin: true
sharp@0.35.4:
resolution: {integrity: sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==}
sharp@0.35.3:
resolution: {integrity: sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==}
engines: {node: '>=20.9.0'}
peerDependencies:
'@types/node': '*'
@@ -928,6 +930,7 @@ packages:
verror@1.10.0:
resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==}
engines: {'0': node >=0.6.0}
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
@@ -1001,132 +1004,132 @@ snapshots:
tslib: 2.8.1
optional: true
'@frsource/base64@1.0.237': {}
'@frsource/base64@1.0.213': {}
'@frsource/cypress-plugin-visual-regression-diff@4.2.1(cypress@16.1.1)':
'@frsource/cypress-plugin-visual-regression-diff@4.1.1(cypress@15.21.1)':
dependencies:
'@frsource/base64': 1.0.237
cypress: 16.1.1
'@frsource/base64': 1.0.213
cypress: 15.21.1
glob: 13.0.6
meta-png: 1.0.6
move-file: 4.1.0
pixelmatch: 7.2.0
pngjs: 7.0.0
sharp: 0.35.4
sharp: 0.35.3
transitivePeerDependencies:
- '@types/node'
'@img/colour@1.1.0': {}
'@img/sharp-darwin-arm64@0.35.4':
'@img/sharp-darwin-arm64@0.35.3':
optionalDependencies:
'@img/sharp-libvips-darwin-arm64': 1.3.3
'@img/sharp-libvips-darwin-arm64': 1.3.2
optional: true
'@img/sharp-darwin-x64@0.35.4':
'@img/sharp-darwin-x64@0.35.3':
optionalDependencies:
'@img/sharp-libvips-darwin-x64': 1.3.3
'@img/sharp-libvips-darwin-x64': 1.3.2
optional: true
'@img/sharp-freebsd-wasm32@0.35.4':
'@img/sharp-freebsd-wasm32@0.35.3':
dependencies:
'@img/sharp-wasm32': 0.35.4
'@img/sharp-wasm32': 0.35.3
optional: true
'@img/sharp-libvips-darwin-arm64@1.3.3':
'@img/sharp-libvips-darwin-arm64@1.3.2':
optional: true
'@img/sharp-libvips-darwin-x64@1.3.3':
'@img/sharp-libvips-darwin-x64@1.3.2':
optional: true
'@img/sharp-libvips-linux-arm64@1.3.3':
'@img/sharp-libvips-linux-arm64@1.3.2':
optional: true
'@img/sharp-libvips-linux-arm@1.3.3':
'@img/sharp-libvips-linux-arm@1.3.2':
optional: true
'@img/sharp-libvips-linux-ppc64@1.3.3':
'@img/sharp-libvips-linux-ppc64@1.3.2':
optional: true
'@img/sharp-libvips-linux-riscv64@1.3.3':
'@img/sharp-libvips-linux-riscv64@1.3.2':
optional: true
'@img/sharp-libvips-linux-s390x@1.3.3':
'@img/sharp-libvips-linux-s390x@1.3.2':
optional: true
'@img/sharp-libvips-linux-x64@1.3.3':
'@img/sharp-libvips-linux-x64@1.3.2':
optional: true
'@img/sharp-libvips-linuxmusl-arm64@1.3.3':
'@img/sharp-libvips-linuxmusl-arm64@1.3.2':
optional: true
'@img/sharp-libvips-linuxmusl-x64@1.3.3':
'@img/sharp-libvips-linuxmusl-x64@1.3.2':
optional: true
'@img/sharp-linux-arm64@0.35.4':
'@img/sharp-linux-arm64@0.35.3':
optionalDependencies:
'@img/sharp-libvips-linux-arm64': 1.3.3
'@img/sharp-libvips-linux-arm64': 1.3.2
optional: true
'@img/sharp-linux-arm@0.35.4':
'@img/sharp-linux-arm@0.35.3':
optionalDependencies:
'@img/sharp-libvips-linux-arm': 1.3.3
'@img/sharp-libvips-linux-arm': 1.3.2
optional: true
'@img/sharp-linux-ppc64@0.35.4':
'@img/sharp-linux-ppc64@0.35.3':
optionalDependencies:
'@img/sharp-libvips-linux-ppc64': 1.3.3
'@img/sharp-libvips-linux-ppc64': 1.3.2
optional: true
'@img/sharp-linux-riscv64@0.35.4':
'@img/sharp-linux-riscv64@0.35.3':
optionalDependencies:
'@img/sharp-libvips-linux-riscv64': 1.3.3
'@img/sharp-libvips-linux-riscv64': 1.3.2
optional: true
'@img/sharp-linux-s390x@0.35.4':
'@img/sharp-linux-s390x@0.35.3':
optionalDependencies:
'@img/sharp-libvips-linux-s390x': 1.3.3
'@img/sharp-libvips-linux-s390x': 1.3.2
optional: true
'@img/sharp-linux-x64@0.35.4':
'@img/sharp-linux-x64@0.35.3':
optionalDependencies:
'@img/sharp-libvips-linux-x64': 1.3.3
'@img/sharp-libvips-linux-x64': 1.3.2
optional: true
'@img/sharp-linuxmusl-arm64@0.35.4':
'@img/sharp-linuxmusl-arm64@0.35.3':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-arm64': 1.3.3
'@img/sharp-libvips-linuxmusl-arm64': 1.3.2
optional: true
'@img/sharp-linuxmusl-x64@0.35.4':
'@img/sharp-linuxmusl-x64@0.35.3':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-x64': 1.3.3
'@img/sharp-libvips-linuxmusl-x64': 1.3.2
optional: true
'@img/sharp-wasm32@0.35.4':
'@img/sharp-wasm32@0.35.3':
dependencies:
'@emnapi/runtime': 1.11.3
optional: true
'@img/sharp-webcontainers-wasm32@0.35.4':
'@img/sharp-webcontainers-wasm32@0.35.3':
dependencies:
'@img/sharp-wasm32': 0.35.4
'@img/sharp-wasm32': 0.35.3
optional: true
'@img/sharp-win32-arm64@0.35.4':
'@img/sharp-win32-arm64@0.35.3':
optional: true
'@img/sharp-win32-ia32@0.35.4':
'@img/sharp-win32-ia32@0.35.3':
optional: true
'@img/sharp-win32-x64@0.35.4':
'@img/sharp-win32-x64@0.35.3':
optional: true
'@testing-library/cypress@10.1.3(cypress@16.1.1)':
'@testing-library/cypress@10.1.3(cypress@15.21.1)':
dependencies:
'@babel/runtime': 7.29.7
'@testing-library/dom': 10.4.1
cypress: 16.1.1
cypress: 15.21.1
'@testing-library/dom@10.4.1':
dependencies:
@@ -1277,11 +1280,11 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
cypress-real-events@1.15.1(cypress@16.1.1):
cypress-real-events@1.15.0(cypress@15.21.1):
dependencies:
cypress: 16.1.1
cypress: 15.21.1
cypress@16.1.1:
cypress@15.21.1:
dependencies:
'@cypress/request': 4.0.1
'@cypress/xvfb': 1.2.4(supports-color@8.1.1)
@@ -1299,7 +1302,7 @@ snapshots:
cli-table3: 0.6.1
commander: 6.2.1
common-tags: 1.8.2
dayjs: 1.11.23
dayjs: 1.11.21
debug: 4.4.3(supports-color@8.1.1)
eventemitter2: 6.4.7
execa: 4.1.0
@@ -1330,7 +1333,7 @@ snapshots:
dependencies:
assert-plus: 1.0.0
dayjs@1.11.23: {}
dayjs@1.11.21: {}
debug@3.2.7(supports-color@8.1.1):
dependencies:
@@ -1686,37 +1689,37 @@ snapshots:
semver@7.8.5: {}
sharp@0.35.4:
sharp@0.35.3:
dependencies:
'@img/colour': 1.1.0
detect-libc: 2.1.2
semver: 7.8.5
optionalDependencies:
'@img/sharp-darwin-arm64': 0.35.4
'@img/sharp-darwin-x64': 0.35.4
'@img/sharp-freebsd-wasm32': 0.35.4
'@img/sharp-libvips-darwin-arm64': 1.3.3
'@img/sharp-libvips-darwin-x64': 1.3.3
'@img/sharp-libvips-linux-arm': 1.3.3
'@img/sharp-libvips-linux-arm64': 1.3.3
'@img/sharp-libvips-linux-ppc64': 1.3.3
'@img/sharp-libvips-linux-riscv64': 1.3.3
'@img/sharp-libvips-linux-s390x': 1.3.3
'@img/sharp-libvips-linux-x64': 1.3.3
'@img/sharp-libvips-linuxmusl-arm64': 1.3.3
'@img/sharp-libvips-linuxmusl-x64': 1.3.3
'@img/sharp-linux-arm': 0.35.4
'@img/sharp-linux-arm64': 0.35.4
'@img/sharp-linux-ppc64': 0.35.4
'@img/sharp-linux-riscv64': 0.35.4
'@img/sharp-linux-s390x': 0.35.4
'@img/sharp-linux-x64': 0.35.4
'@img/sharp-linuxmusl-arm64': 0.35.4
'@img/sharp-linuxmusl-x64': 0.35.4
'@img/sharp-webcontainers-wasm32': 0.35.4
'@img/sharp-win32-arm64': 0.35.4
'@img/sharp-win32-ia32': 0.35.4
'@img/sharp-win32-x64': 0.35.4
'@img/sharp-darwin-arm64': 0.35.3
'@img/sharp-darwin-x64': 0.35.3
'@img/sharp-freebsd-wasm32': 0.35.3
'@img/sharp-libvips-darwin-arm64': 1.3.2
'@img/sharp-libvips-darwin-x64': 1.3.2
'@img/sharp-libvips-linux-arm': 1.3.2
'@img/sharp-libvips-linux-arm64': 1.3.2
'@img/sharp-libvips-linux-ppc64': 1.3.2
'@img/sharp-libvips-linux-riscv64': 1.3.2
'@img/sharp-libvips-linux-s390x': 1.3.2
'@img/sharp-libvips-linux-x64': 1.3.2
'@img/sharp-libvips-linuxmusl-arm64': 1.3.2
'@img/sharp-libvips-linuxmusl-x64': 1.3.2
'@img/sharp-linux-arm': 0.35.3
'@img/sharp-linux-arm64': 0.35.3
'@img/sharp-linux-ppc64': 0.35.3
'@img/sharp-linux-riscv64': 0.35.3
'@img/sharp-linux-s390x': 0.35.3
'@img/sharp-linux-x64': 0.35.3
'@img/sharp-linuxmusl-arm64': 0.35.3
'@img/sharp-linuxmusl-x64': 0.35.3
'@img/sharp-webcontainers-wasm32': 0.35.3
'@img/sharp-win32-arm64': 0.35.3
'@img/sharp-win32-ia32': 0.35.3
'@img/sharp-win32-x64': 0.35.3
shebang-command@2.0.0:
dependencies:

View File

@@ -16,7 +16,7 @@ globalThis.process.env = {
Cypress.Screenshot.defaults({ capture: 'viewport' })
if (Cypress.expose('CY_CI')) {
if (Cypress.env('CY_CI')) {
Cypress.config('defaultCommandTimeout', 20000)
}

View File

@@ -4,4 +4,4 @@ corepack enable pnpm
pnpm install --frozen-lockfile --ignore-scripts
pnpm cypress:install
pnpm test:ci:ct --expose pluginVisualRegressionUpdateImages=$CYPRESS_UPDATE_SNAPSHOTS --spec '../../**/*-visuals.cy.*'
pnpm test:ci:ct --env pluginVisualRegressionUpdateImages=$CYPRESS_UPDATE_SNAPSHOTS --spec '../../**/*-visuals.cy.*'

View File

@@ -32,7 +32,7 @@ services:
command: sleep infinity
elasticsearch:
image: elasticsearch:9.5.3
image: elasticsearch:9.3.2
environment:
- "ES_JAVA_OPTS=-Xms1024m -Xmx1024m"
- discovery.type=single-node
@@ -56,7 +56,7 @@ services:
memcached:
command: memcached -m 256M
image: memcached:1.6.45-alpine
image: memcached:1.6.38-alpine
restart: unless-stopped
postgresql:

View File

@@ -13,9 +13,9 @@ pnpm config set store-dir "$(pwd)/.pnpm-store" --global
bin/setup --skip-server
echo "== Initialize CodeGraph index =="
echo "== Install CodeGraph index =="
pnpm run codegraph:init
pnpm run codegraph:install
echo "== Precompile Ruby cache =="

View File

@@ -13,9 +13,9 @@ pnpm config set store-dir "$(pwd)/.pnpm-store" --global
bin/setup --skip-server
echo "== Initialize CodeGraph index =="
echo "== Install CodeGraph index =="
pnpm run codegraph:init
pnpm run codegraph:install
echo "== Precompile Ruby cache =="

View File

@@ -13,9 +13,9 @@ pnpm config set store-dir "$(pwd)/.pnpm-store" --global
bin/setup --skip-server
echo "== Initialize CodeGraph index =="
echo "== Install CodeGraph index =="
pnpm run codegraph:init
pnpm run codegraph:install
echo "== Precompile Ruby cache =="

View File

@@ -13,9 +13,9 @@ pnpm config set store-dir "$(pwd)/.pnpm-store" --global
bin/setup --skip-server
echo "== Initialize CodeGraph index =="
echo "== Install CodeGraph index =="
pnpm run codegraph:init
pnpm run codegraph:install
echo "== Precompile Ruby cache =="

View File

@@ -17,10 +17,10 @@
"requireindex": "^1.2.0"
},
"devDependencies": {
"eslint": "^10.12.0",
"eslint": "^10.9.1",
"eslint-plugin-eslint-plugin": "^7.6.2",
"eslint-plugin-n": "^18.4.0",
"mocha": "^12.0.3",
"eslint-plugin-n": "^18.3.0",
"mocha": "^11.8.0",
"vue-eslint-parser": "^10.4.1"
},
"engines": {

View File

@@ -13,29 +13,23 @@ importers:
version: 1.2.0
devDependencies:
eslint:
specifier: ^10.12.0
version: 10.12.0(supports-color@8.1.1)
specifier: ^10.9.1
version: 10.9.1(supports-color@8.1.1)
eslint-plugin-eslint-plugin:
specifier: ^7.6.2
version: 7.6.2(eslint@10.12.0(supports-color@8.1.1))
version: 7.6.2(eslint@10.9.1(supports-color@8.1.1))
eslint-plugin-n:
specifier: ^18.4.0
version: 18.4.0(eslint@10.12.0(supports-color@8.1.1))
specifier: ^18.3.0
version: 18.3.0(eslint@10.9.1(supports-color@8.1.1))
mocha:
specifier: ^12.0.3
version: 12.0.3
specifier: ^11.8.0
version: 11.8.0
vue-eslint-parser:
specifier: ^10.4.1
version: 10.4.1(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1)
version: 10.4.1(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1)
packages:
'@cacheable/memory@2.2.0':
resolution: {integrity: sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==}
'@cacheable/utils@2.5.0':
resolution: {integrity: sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==}
'@eslint-community/eslint-utils@4.10.1':
resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -62,8 +56,8 @@ packages:
resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
'@eslint/plugin-kit@0.7.3':
resolution: {integrity: sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==}
'@eslint/plugin-kit@0.7.2':
resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
'@humanfs/core@0.19.2':
@@ -86,14 +80,13 @@ packages:
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'}
'@keyv/bigmap@1.3.1':
resolution: {integrity: sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==}
engines: {node: '>= 18'}
peerDependencies:
keyv: ^5.6.0
'@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
'@keyv/serialize@1.1.1':
resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==}
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
'@types/esrecurse@4.3.1':
resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==}
@@ -117,13 +110,35 @@ packages:
ajv@6.15.0:
resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
ansi-regex@6.3.0:
resolution: {integrity: sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==}
engines: {node: '>=12'}
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
ansi-styles@6.2.3:
resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
engines: {node: '>=12'}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
balanced-match@4.0.4:
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
engines: {node: 18 || 20 || >=22}
brace-expansion@2.1.4:
resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==}
brace-expansion@5.0.9:
resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==}
engines: {node: 20 || >=22}
@@ -131,12 +146,28 @@ packages:
browser-stdout@1.3.1:
resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==}
cacheable@2.5.0:
resolution: {integrity: sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==}
camelcase@6.3.0:
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
engines: {node: '>=10'}
chokidar@5.0.0:
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
engines: {node: '>= 20.19.0'}
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
chokidar@4.0.3:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'}
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
@@ -151,17 +182,34 @@ packages:
supports-color:
optional: true
decamelize@4.0.0:
resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==}
engines: {node: '>=10'}
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
diff@9.0.0:
resolution: {integrity: sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==}
diff@7.0.0:
resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==}
engines: {node: '>=0.3.1'}
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
enhanced-resolve@5.24.5:
resolution: {integrity: sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==}
engines: {node: '>=10.13.0'}
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
@@ -184,8 +232,8 @@ packages:
peerDependencies:
eslint: '>=9.0.0'
eslint-plugin-n@18.4.0:
resolution: {integrity: sha512-TEm6Nqn9+l7EWfJXOL6pmrEdpiM2ZTgo4fNMRDvBTM70jeQ7x3X3Akcx+sRCIRfpcSVwMypH3ZAA33PLyNKmYw==}
eslint-plugin-n@18.3.0:
resolution: {integrity: sha512-cPVguuDe6DrIPb/qUXHf8P89MaVTUmiYWwpt5gX5AILsvRIiZAxMFXcFR6QHYBksqKJpjfUBlL/RleCJUWcD7w==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
peerDependencies:
eslint: '>=8.57.1'
@@ -209,8 +257,8 @@ packages:
resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
eslint@10.12.0:
resolution: {integrity: sha512-npHjrHb2o4ehH3mE+YuxABR+0gyb0aVWCIolgJDArwCiHEIsonBm3ZKjel5b0U5+a+MbEvIyRCGCzD3eKKK2NA==}
eslint@10.9.1:
resolution: {integrity: sha512-9VaAkDURekixUQJy0oJYl2DcN6oKMfxay7XzaGYAWQwsb6qfKf+x76R2k1L8kb1boc+FyCAaTA9GmiKaaiaF+A==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
hasBin: true
peerDependencies:
@@ -248,23 +296,33 @@ packages:
fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
file-entry-cache@11.1.5:
resolution: {integrity: sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==}
find-up-simple@1.0.1:
resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==}
engines: {node: '>=18'}
file-entry-cache@8.0.0:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
flat-cache@6.1.23:
resolution: {integrity: sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==}
flat-cache@4.0.1:
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
engines: {node: '>=16'}
flat@5.0.2:
resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}
hasBin: true
flatted@3.4.4:
resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==}
foreground-child@3.3.1:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'}
get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
get-tsconfig@4.14.2:
resolution: {integrity: sha512-XpwZALwwl/BaKTAyC6+c5T8y6kCg2jk+XGqOVrKIQmW49pNypYLMRjCUXqa28tQgJlhS2RlzP7sc+Rx7W6qsfw==}
@@ -272,9 +330,10 @@ packages:
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
engines: {node: '>=10.13.0'}
glob@13.0.6:
resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==}
engines: {node: 18 || 20 || >=22}
glob@10.5.0:
resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
hasBin: true
globals@15.15.0:
resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==}
@@ -290,15 +349,9 @@ packages:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
hashery@1.5.1:
resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==}
engines: {node: '>=20'}
hookified@1.15.1:
resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==}
hookified@2.2.0:
resolution: {integrity: sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==}
he@1.2.0:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
@@ -312,13 +365,21 @@ packages:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
is-path-inside@4.0.0:
resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==}
engines: {node: '>=12'}
is-path-inside@3.0.3:
resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
engines: {node: '>=8'}
is-plain-obj@2.1.0:
resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==}
engines: {node: '>=8'}
is-unicode-supported@0.1.0:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
@@ -327,18 +388,24 @@ packages:
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
js-yaml@5.4.1:
resolution: {integrity: sha512-28R/k+NAjeuf7+CKlTxWZVExJGwVVLwY06DgEnOMz2gEpfNkDcD7QvyiVPT0xy0XXhU8vHsd4Ot42OOPdJG7dQ==}
jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
js-yaml@4.3.1:
resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==}
hasBin: true
json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
keyv@5.6.0:
resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==}
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
@@ -348,21 +415,28 @@ packages:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
lru-cache@11.5.2:
resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==}
engines: {node: 20 || >=22}
log-symbols@4.1.0:
resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
engines: {node: '>=10'}
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
minimatch@10.2.6:
resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==}
engines: {node: 18 || 20 || >=22}
minimatch@9.0.9:
resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==}
engines: {node: '>=16 || 14 >=14.17'}
minipass@7.1.3:
resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
engines: {node: '>=16 || 14 >=14.17'}
mocha@12.0.3:
resolution: {integrity: sha512-beammieDdiWdkjANWeWPpkxUROYGi5FN88fPOkLiticz7HTg3Y+Pho3EYE6SUdcuWnZ3h2WD8EXCA55JF4o6wg==}
engines: {node: ^20.19.0 || >=22.12.0}
mocha@11.8.0:
resolution: {integrity: sha512-VyCeUdGN3A9lmCTTgG4yuvY9ixxaDk+xt2R/7/+1AP6EqNG+G9OKkzBwhVtVYoNX8YsxNSgAl8mOv3IAeOpFbw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
hasBin: true
ms@2.1.3:
@@ -383,6 +457,9 @@ packages:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@@ -391,9 +468,9 @@ packages:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
path-scurry@2.0.2:
resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==}
engines: {node: 18 || 20 || >=22}
path-scurry@1.11.1:
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
engines: {node: '>=16 || 14 >=14.18'}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -406,13 +483,16 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
qified@0.10.1:
resolution: {integrity: sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==}
engines: {node: '>=20'}
randombytes@2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
readdirp@5.1.1:
resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==}
engines: {node: '>= 20.19.0'}
readdirp@4.1.2:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'}
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
requireindex@1.2.0:
resolution: {integrity: sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==}
@@ -421,14 +501,16 @@ packages:
resolve-pkg-maps@1.0.0:
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
semver@7.8.5:
resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==}
engines: {node: '>=10'}
hasBin: true
serialize-javascript@7.1.1:
resolution: {integrity: sha512-k3CMsaIvvdSwm8oLB4MXSl0wH2/cwlH7xGcnRd2DaeRmBkbzYmyT8j0tsX60DwD1eRwHTpNpH8ljKu9oUT1MeQ==}
engines: {node: '>=20.0.0'}
serialize-javascript@6.0.2:
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
@@ -438,9 +520,33 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
strip-json-comments@5.0.3:
resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==}
engines: {node: '>=14.16'}
signal-exit@4.1.0:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
string-width@5.1.2:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'}
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
strip-ansi@7.2.0:
resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==}
engines: {node: '>=12'}
strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
supports-color@8.1.1:
resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
@@ -472,8 +578,32 @@ packages:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
workerpool@10.0.3:
resolution: {integrity: sha512-6z2Iis68Wqth93/G/wJP9u+R3O+d2XTlgWChGCwuT1qLbBsOYueGRZuJ++v3mtDP5KjYdy+WzvWC+VWETSVXJA==}
workerpool@9.3.4:
resolution: {integrity: sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==}
wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
wrap-ansi@8.1.0:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'}
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
yargs-unparser@2.0.0:
resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==}
engines: {node: '>=10'}
yargs@17.7.3:
resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==}
engines: {node: '>=12'}
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
@@ -481,21 +611,9 @@ packages:
snapshots:
'@cacheable/memory@2.2.0':
'@eslint-community/eslint-utils@4.10.1(eslint@10.9.1(supports-color@8.1.1))':
dependencies:
'@cacheable/utils': 2.5.0
'@keyv/bigmap': 1.3.1(keyv@5.6.0)
hookified: 1.15.1
keyv: 5.6.0
'@cacheable/utils@2.5.0':
dependencies:
hashery: 1.5.1
keyv: 5.6.0
'@eslint-community/eslint-utils@4.10.1(eslint@10.12.0(supports-color@8.1.1))':
dependencies:
eslint: 10.12.0(supports-color@8.1.1)
eslint: 10.9.1(supports-color@8.1.1)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.2': {}
@@ -518,7 +636,7 @@ snapshots:
'@eslint/object-schema@3.0.5': {}
'@eslint/plugin-kit@0.7.3':
'@eslint/plugin-kit@0.7.2':
dependencies:
'@eslint/core': 1.2.1
levn: 0.4.1
@@ -539,13 +657,17 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {}
'@keyv/bigmap@1.3.1(keyv@5.6.0)':
'@isaacs/cliui@8.0.2':
dependencies:
hashery: 1.5.1
hookified: 1.15.1
keyv: 5.6.0
string-width: 5.1.2
string-width-cjs: string-width@4.2.3
strip-ansi: 7.2.0
strip-ansi-cjs: strip-ansi@6.0.1
wrap-ansi: 8.1.0
wrap-ansi-cjs: wrap-ansi@7.0.0
'@keyv/serialize@1.1.1': {}
'@pkgjs/parseargs@0.11.0':
optional: true
'@types/esrecurse@4.3.1': {}
@@ -566,27 +688,54 @@ snapshots:
json-schema-traverse: 0.4.1
uri-js: 4.4.1
ansi-regex@5.0.1: {}
ansi-regex@6.3.0: {}
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
ansi-styles@6.2.3: {}
argparse@2.0.1: {}
balanced-match@1.0.2: {}
balanced-match@4.0.4: {}
brace-expansion@2.1.4:
dependencies:
balanced-match: 1.0.2
brace-expansion@5.0.9:
dependencies:
balanced-match: 4.0.4
browser-stdout@1.3.1: {}
cacheable@2.5.0:
dependencies:
'@cacheable/memory': 2.2.0
'@cacheable/utils': 2.5.0
hookified: 1.15.1
keyv: 5.6.0
qified: 0.10.1
camelcase@6.3.0: {}
chokidar@5.0.0:
chalk@4.1.2:
dependencies:
readdirp: 5.1.1
ansi-styles: 4.3.0
supports-color: 7.2.0
chokidar@4.0.3:
dependencies:
readdirp: 4.1.2
cliui@8.0.1:
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
color-name@1.1.4: {}
cross-spawn@7.0.6:
dependencies:
@@ -600,46 +749,55 @@ snapshots:
optionalDependencies:
supports-color: 8.1.1
decamelize@4.0.0: {}
deep-is@0.1.4: {}
diff@9.0.0: {}
diff@7.0.0: {}
eastasianwidth@0.2.0: {}
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
enhanced-resolve@5.24.5:
dependencies:
graceful-fs: 4.2.11
tapable: 2.3.3
escalade@3.2.0: {}
escape-string-regexp@4.0.0: {}
eslint-compat-utils@0.5.1(eslint@10.12.0(supports-color@8.1.1)):
eslint-compat-utils@0.5.1(eslint@10.9.1(supports-color@8.1.1)):
dependencies:
eslint: 10.12.0(supports-color@8.1.1)
eslint: 10.9.1(supports-color@8.1.1)
semver: 7.8.5
eslint-plugin-es-x@7.8.0(eslint@10.12.0(supports-color@8.1.1)):
eslint-plugin-es-x@7.8.0(eslint@10.9.1(supports-color@8.1.1)):
dependencies:
'@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(supports-color@8.1.1))
'@eslint-community/eslint-utils': 4.10.1(eslint@10.9.1(supports-color@8.1.1))
'@eslint-community/regexpp': 4.12.2
eslint: 10.12.0(supports-color@8.1.1)
eslint-compat-utils: 0.5.1(eslint@10.12.0(supports-color@8.1.1))
eslint: 10.9.1(supports-color@8.1.1)
eslint-compat-utils: 0.5.1(eslint@10.9.1(supports-color@8.1.1))
eslint-plugin-eslint-plugin@7.6.2(eslint@10.12.0(supports-color@8.1.1)):
eslint-plugin-eslint-plugin@7.6.2(eslint@10.9.1(supports-color@8.1.1)):
dependencies:
'@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(supports-color@8.1.1))
eslint: 10.12.0(supports-color@8.1.1)
'@eslint-community/eslint-utils': 4.10.1(eslint@10.9.1(supports-color@8.1.1))
eslint: 10.9.1(supports-color@8.1.1)
estraverse: 5.3.0
eslint-plugin-n@18.4.0(eslint@10.12.0(supports-color@8.1.1)):
eslint-plugin-n@18.3.0(eslint@10.9.1(supports-color@8.1.1)):
dependencies:
'@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(supports-color@8.1.1))
'@eslint-community/eslint-utils': 4.10.1(eslint@10.9.1(supports-color@8.1.1))
enhanced-resolve: 5.24.5
eslint: 10.12.0(supports-color@8.1.1)
eslint-plugin-es-x: 7.8.0(eslint@10.12.0(supports-color@8.1.1))
eslint: 10.9.1(supports-color@8.1.1)
eslint-plugin-es-x: 7.8.0(eslint@10.9.1(supports-color@8.1.1))
get-tsconfig: 4.14.2
globals: 15.15.0
globrex: 0.1.2
ignore: 5.3.2
js-yaml: 5.4.1
semver: 7.8.5
eslint-scope@9.1.2:
@@ -653,14 +811,14 @@ snapshots:
eslint-visitor-keys@5.0.1: {}
eslint@10.12.0(supports-color@8.1.1):
eslint@10.9.1(supports-color@8.1.1):
dependencies:
'@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(supports-color@8.1.1))
'@eslint-community/eslint-utils': 4.10.1(eslint@10.9.1(supports-color@8.1.1))
'@eslint-community/regexpp': 4.12.2
'@eslint/config-array': 0.23.5(supports-color@8.1.1)
'@eslint/config-helpers': 0.7.0
'@eslint/core': 1.2.1
'@eslint/plugin-kit': 0.7.3
'@eslint/plugin-kit': 0.7.2
'@humanfs/node': 0.16.8
'@humanwhocodes/module-importer': 1.0.1
'@humanwhocodes/retry': 0.4.3
@@ -675,7 +833,7 @@ snapshots:
esquery: 1.7.0
esutils: 2.0.3
fast-deep-equal: 3.1.3
file-entry-cache: 11.1.5
file-entry-cache: 8.0.0
find-up: 5.0.0
glob-parent: 6.0.2
ignore: 5.3.2
@@ -712,25 +870,31 @@ snapshots:
fast-levenshtein@2.0.6: {}
file-entry-cache@11.1.5:
file-entry-cache@8.0.0:
dependencies:
flat-cache: 6.1.23
find-up-simple@1.0.1: {}
flat-cache: 4.0.1
find-up@5.0.0:
dependencies:
locate-path: 6.0.0
path-exists: 4.0.0
flat-cache@6.1.23:
flat-cache@4.0.1:
dependencies:
cacheable: 2.5.0
flatted: 3.4.4
hookified: 1.15.1
keyv: 4.5.4
flat@5.0.2: {}
flatted@3.4.4: {}
foreground-child@3.3.1:
dependencies:
cross-spawn: 7.0.6
signal-exit: 4.1.0
get-caller-file@2.0.5: {}
get-tsconfig@4.14.2:
dependencies:
resolve-pkg-maps: 1.0.0
@@ -739,11 +903,14 @@ snapshots:
dependencies:
is-glob: 4.0.3
glob@13.0.6:
glob@10.5.0:
dependencies:
minimatch: 10.2.6
foreground-child: 3.3.1
jackspeak: 3.4.3
minimatch: 9.0.9
minipass: 7.1.3
path-scurry: 2.0.2
package-json-from-dist: 1.0.1
path-scurry: 1.11.1
globals@15.15.0: {}
@@ -753,13 +920,7 @@ snapshots:
has-flag@4.0.0: {}
hashery@1.5.1:
dependencies:
hookified: 1.15.1
hookified@1.15.1: {}
hookified@2.2.0: {}
he@1.2.0: {}
ignore@5.3.2: {}
@@ -767,27 +928,39 @@ snapshots:
is-extglob@2.1.1: {}
is-fullwidth-code-point@3.0.0: {}
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
is-path-inside@4.0.0: {}
is-path-inside@3.0.3: {}
is-plain-obj@2.1.0: {}
is-unicode-supported@0.1.0: {}
isexe@2.0.0: {}
js-yaml@5.4.1:
jackspeak@3.4.3:
dependencies:
'@isaacs/cliui': 8.0.2
optionalDependencies:
'@pkgjs/parseargs': 0.11.0
js-yaml@4.3.1:
dependencies:
argparse: 2.0.1
json-buffer@3.0.1: {}
json-schema-traverse@0.4.1: {}
json-stable-stringify-without-jsonify@1.0.1: {}
keyv@5.6.0:
keyv@4.5.4:
dependencies:
'@keyv/serialize': 1.1.1
json-buffer: 3.0.1
levn@0.4.1:
dependencies:
@@ -798,32 +971,46 @@ snapshots:
dependencies:
p-locate: 5.0.0
lru-cache@11.5.2: {}
log-symbols@4.1.0:
dependencies:
chalk: 4.1.2
is-unicode-supported: 0.1.0
lru-cache@10.4.3: {}
minimatch@10.2.6:
dependencies:
brace-expansion: 5.0.9
minimatch@9.0.9:
dependencies:
brace-expansion: 2.1.4
minipass@7.1.3: {}
mocha@12.0.3:
mocha@11.8.0:
dependencies:
browser-stdout: 1.3.1
chokidar: 5.0.0
chokidar: 4.0.3
debug: 4.4.3(supports-color@8.1.1)
diff: 9.0.0
find-up-simple: 1.0.1
glob: 13.0.6
is-path-inside: 4.0.0
is-unicode-supported: 0.1.0
js-yaml: 5.4.1
minimatch: 10.2.6
diff: 7.0.0
escape-string-regexp: 4.0.0
find-up: 5.0.0
glob: 10.5.0
he: 1.2.0
is-path-inside: 3.0.3
js-yaml: 4.3.1
log-symbols: 4.1.0
minimatch: 9.0.9
ms: 2.1.3
picocolors: 1.1.1
serialize-javascript: 7.1.1
strip-json-comments: 5.0.3
serialize-javascript: 6.0.2
strip-json-comments: 3.1.1
supports-color: 8.1.1
workerpool: 10.0.3
workerpool: 9.3.4
yargs: 17.7.3
yargs-parser: 21.1.1
yargs-unparser: 2.0.0
ms@2.1.3: {}
@@ -846,13 +1033,15 @@ snapshots:
dependencies:
p-limit: 3.1.0
package-json-from-dist@1.0.1: {}
path-exists@4.0.0: {}
path-key@3.1.1: {}
path-scurry@2.0.2:
path-scurry@1.11.1:
dependencies:
lru-cache: 11.5.2
lru-cache: 10.4.3
minipass: 7.1.3
picocolors@1.1.1: {}
@@ -861,19 +1050,25 @@ snapshots:
punycode@2.3.1: {}
qified@0.10.1:
randombytes@2.1.0:
dependencies:
hookified: 2.2.0
safe-buffer: 5.2.1
readdirp@5.1.1: {}
readdirp@4.1.2: {}
require-directory@2.1.1: {}
requireindex@1.2.0: {}
resolve-pkg-maps@1.0.0: {}
safe-buffer@5.2.1: {}
semver@7.8.5: {}
serialize-javascript@7.1.1: {}
serialize-javascript@6.0.2:
dependencies:
randombytes: 2.1.0
shebang-command@2.0.0:
dependencies:
@@ -881,7 +1076,33 @@ snapshots:
shebang-regex@3.0.0: {}
strip-json-comments@5.0.3: {}
signal-exit@4.1.0: {}
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
string-width@5.1.2:
dependencies:
eastasianwidth: 0.2.0
emoji-regex: 9.2.2
strip-ansi: 7.2.0
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
strip-ansi@7.2.0:
dependencies:
ansi-regex: 6.3.0
strip-json-comments@3.1.1: {}
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
supports-color@8.1.1:
dependencies:
@@ -897,10 +1118,10 @@ snapshots:
dependencies:
punycode: 2.3.1
vue-eslint-parser@10.4.1(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1):
vue-eslint-parser@10.4.1(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1):
dependencies:
debug: 4.4.3(supports-color@8.1.1)
eslint: 10.12.0(supports-color@8.1.1)
eslint: 10.9.1(supports-color@8.1.1)
eslint-scope: 9.1.2
eslint-visitor-keys: 5.0.1
espree: 11.2.0
@@ -915,6 +1136,39 @@ snapshots:
word-wrap@1.2.5: {}
workerpool@10.0.3: {}
workerpool@9.3.4: {}
wrap-ansi@7.0.0:
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi@8.1.0:
dependencies:
ansi-styles: 6.2.3
string-width: 5.1.2
strip-ansi: 7.2.0
y18n@5.0.8: {}
yargs-parser@21.1.1: {}
yargs-unparser@2.0.0:
dependencies:
camelcase: 6.3.0
decamelize: 4.0.0
flat: 5.0.2
is-plain-obj: 2.1.0
yargs@17.7.3:
dependencies:
cliui: 8.0.1
escalade: 3.2.0
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 21.1.1
yocto-queue@0.1.0: {}

View File

@@ -22,6 +22,7 @@ jobs:
fail-fast: false
matrix:
target:
- debian:11
- debian:12
- debian:13
- el:9

3
.gitignore vendored
View File

@@ -107,7 +107,6 @@
# Node
**/node_modules
/.vitest
yarn-error.log
.yarn/cache
/.pnpm-store
@@ -142,6 +141,6 @@ schema.graphql
/.claude/skills
/.claude/settings.local.json
# CodeGraph
# Codegraph
/.codegraph/
/.mcp.json

View File

@@ -1,90 +0,0 @@
#!/usr/bin/env ruby
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
# Ensures that all precompiled gems in Gemfile.lock are available for every platform
# Zammad is built for (packager.io, Docker, CI, development machines), so that none of them
# falls back to compiling its native extension from source.
#
# .gitlab/check_native_gem_platforms.rb [path/to/Gemfile.lock]
require 'bundler'
class CheckNativeGemPlatforms
# `ruby` is the fallback for all unlisted platforms. Bundler silently drops it when a new gem
# is added to the Gemfile, which would break installations on those platforms.
REQUIRED_PLATFORMS = %w[ruby x86_64-linux-gnu aarch64-linux-gnu x86_64-darwin arm64-darwin].freeze
# An update can leave only the source variant of a gem in the lockfile, e.g. when its precompiled
# gems are not published yet. Listing the gems keeps them checked even then.
PRECOMPILED_GEMS = %w[ffi google-protobuf nokogiri pg sass-embedded].freeze
def self.run(lockfile_path)
new(lockfile_path).run
end
def initialize(lockfile_path)
@lockfile = Bundler::LockfileParser.new(File.read(lockfile_path))
@errors = []
end
def run
puts 'Checking native gem platforms in Gemfile.lock…'
check_required_platforms
check_precompiled_gems
if @errors.any?
puts(@errors.map { |error| " #{error}" })
exit 1
end
puts " #{@lockfile.platforms.size} platforms present, #{precompiled_gems.size} precompiled gems (#{precompiled_gems.keys.sort.join(', ')}) available for all #{binary_platforms.size} binary platforms."
puts 'done.'
end
private
def check_required_platforms
platforms = @lockfile.platforms.map(&:to_s)
(REQUIRED_PLATFORMS - platforms).each do |platform|
@errors << "Platform '#{platform}' is missing, restore it with: bundle lock --add-platform #{platform}"
end
end
def check_precompiled_gems
precompiled_gems.each do |name, variants|
# This script runs without ActiveSupport, so Array#exclude? is not available.
@errors << "#{name} is a precompiled gem, add it to PRECOMPILED_GEMS in #{__FILE__}." if !PRECOMPILED_GEMS.include?(name) # rubocop:disable Rails/NegateInclude
binary_platforms.each do |platform|
next if variants.any? { |spec| binary?(spec) && Gem::Platform.new(spec.platform) === platform } # rubocop:disable Style/CaseEquality
@errors << "#{name} #{variants.first.version} has no precompiled gem for #{platform}#{' and would need a Rust toolchain' if rust?(variants)}."
end
end
end
# A gem counts as precompiled if it is listed, or ships at least one platform variant. Rust-based
# gems count even without either, their source variant is recognizable by its rb_sys dependency.
def precompiled_gems
@precompiled_gems ||= @lockfile.specs.group_by(&:name).select do |name, variants|
PRECOMPILED_GEMS.include?(name) || variants.any? { |spec| binary?(spec) } || rust?(variants)
end
end
def rust?(variants)
variants.any? { |spec| !binary?(spec) && spec.dependencies.any? { |dependency| dependency.name == 'rb_sys' } }
end
def binary?(spec)
spec.platform != Gem::Platform::RUBY
end
def binary_platforms
@binary_platforms ||= @lockfile.platforms.reject { |platform| platform == Gem::Platform::RUBY }
end
end
CheckNativeGemPlatforms.run(ARGV[0] || File.expand_path('../Gemfile.lock', __dir__))

View File

@@ -1,5 +0,0 @@
# Certain jobs use no artifacts of the earlier stages. Without this, GitLab downloads all of
# them, and retrying such a job after the database dumps expired fails it with
# `missing_dependency_failure`.
.no_artifact_dependencies:
dependencies: []

View File

@@ -4,6 +4,9 @@ default:
tags:
- docker
# Most jobs execute ruby commands, so take that as default.
cache: !reference [.cache, read_only_ruby]
# Artifacts are stored for failed jobs
artifacts:
expire_in: 1 week
@@ -15,20 +18,16 @@ default:
interruptible: true
# Initialize application env
before_script:
- !reference [.scripts, source_rvm]
- !reference [.scripts, bundle_install]
- !reference [.scripts, configure_environment]
after_script:
- .gitlab/environment_info.sh > log/environment_info.log
# Copy logs from other containers to store them as artifacts.
- cp /builds/*.log log/ || true
# Print recorded test runtimes for script/build/update_test_timings.rb. The script is
# missing if an older commit is checked out, e.g. in rspec:migration.
- if [ -x .gitlab/print_test_timings.sh ]; then .gitlab/print_test_timings.sh; fi
# Logs and screenshots are only needed for failed jobs, see .artifacts_with_test_report.
- if [ "$CI_JOB_STATUS" = success ]; then rm -rf log/*.log tmp/screenshot* tmp/playwright-traces; fi
# For jobs running the application's Ruby code: read the gem cache and initialize the
# application environment.
.template_ruby:
cache: !reference [.cache, read_only_ruby]
before_script:
- !reference [.scripts, source_rvm]
- !reference [.scripts, bundle_install]
- !reference [.scripts, configure_environment]

View File

@@ -1,48 +0,0 @@
# Building blocks for `needs:`, combined per job:
#
# needs:
# - !reference [.needs, ruby]
# - !reference [.needs, lint]
#
# A job with `needs:` starts as soon as the listed jobs have finished, instead of waiting
# for all jobs of the earlier stages, and downloads the artifacts of the listed jobs only.
#
# Jobs of the test and browser stages need `lint`, so that none of them is started for a
# commit that fails linting. The manual lint jobs are not listed, a job needing them
# would wait for them forever.
#
# Jobs with rules of their own are optional, because filtered pipelines ($JOB_FILTER,
# $BROWSER_TEST_FILTER) do not contain them, and needing a job that is missing from the
# pipeline fails its creation. Without 'write database dumps', .gitlab/init_database.sh
# initializes the database from scratch.
.needs:
ruby:
- job: 'write cache: ruby'
artifacts: false
nodejs:
- job: 'write cache: nodejs'
artifacts: false
database:
- job: 'write database dumps'
optional: true
assets:
- job: 'assets:build'
lint:
- job: 'lint: shellcheck'
artifacts: false
optional: true
- job: 'lint: i18n & rails'
artifacts: false
optional: true
- job: 'lint: ruby & security'
artifacts: false
optional: true
- job: 'lint: markdown, coffee & css'
artifacts: false
optional: true
- job: 'lint: js'
artifacts: false
optional: true
- job: 'lint: secret_detection'
artifacts: false
optional: true

View File

@@ -1,65 +1,5 @@
# Rules decide whether a job is added to a pipeline at all, and if so, whether it
# starts automatically, waits for a manual start, and may fail. Entries are
# evaluated from top to bottom, the first matching one wins.
#
# This file provides two kinds of building blocks, which must not be confused:
#
# 1. Fragments below `.rule_fragments` - *partial* rules lists, spliced into the
# list of a job with `!reference`:
#
# rules:
# - !reference [.rule_fragments, main_repository_only]
# - !reference [.rule_fragments, variable_filters]
# - if: $SOMETHING
# when: on_success
# - !reference [.rule_fragments, then_manual]
#
# Fragments have no `rules:` key of their own, so using one in `extends:` splices
# its keys into the job and GitLab rejects it - never use them there.
#
# 2. Rule sets named `.job_rules_*` - a *complete* rules list, used with
# `extends:`. There is deliberately only one of them, see below.
#
# Conventions:
#
# * A `.job_rules_*` set must end in an unconditional entry whose `when` is not
# `never`, so that extending it always yields a job that runs somewhere.
# Anything that only ever excludes (like `main_repository_only`), or that ends
# in `when: never`, has to be a fragment. This is the mistake to worry about:
# unlike a fragment in `extends:`, which GitLab rejects, a set that can only
# exclude is accepted without error or warning and the job is simply never
# created in any pipeline.
# * A job's own `rules:` replaces every inherited list rather than merging with
# it. Never add a rule set to a job or template that already inherits one -
# the reader cannot see which of the two wins. Write the deviating list at the
# job as `rules:` instead.
# * `then_*` fragments contain the closing, unconditional entry of a list and
# therefore appear last and exactly once. Every other fragment contributes
# only conditional entries and can be placed anywhere before that.
# * Order within a list: guards (`main_repository_only`) first, then the pipeline
# filters (`variable_filters`), then the job's own conditions, then the closing
# entry. A guard placed further down is dead code.
# * Spell out `when:` in every entry, including `when: on_success`. An entry
# without it reads as if it would not start the job.
# * `$JOB_FILTER` is matched against `$CI_JOB_NAME`, which for `parallel` and
# `matrix` jobs contains the shard suffix (e.g. `rspec 1/8`), so those jobs can
# only be selected with their full name.
#
# Every job needs rules of its own to honour $JOB_FILTER: `default:` cannot carry
# `rules:`, and a job without any `rules:` runs unconditionally. The few jobs that
# want exactly that - because filtered pipelines still need their cache or their
# artifacts - say so in a comment.
.rule_fragments:
# Never run in forks, e.g. because project specific secrets or runners are needed.
main_repository_only:
- if: $CI_PROJECT_PATH != "zammad/zammad"
when: never
# Honour the JOB_FILTER and BROWSER_TEST_FILTER push variables, which reduce a
# pipeline to a single job (see workflow.yml). A matching JOB_FILTER *forces*
# the job to run, whatever its own conditions say.
.job_rules:
# Allow filtering CI pipelines with JOB_FILTER and BROWSER_TEST_FILTER push variabales.
variable_filters:
- if: $BROWSER_TEST_FILTER
when: never
@@ -68,51 +8,22 @@
- if: $JOB_FILTER && $JOB_FILTER != $CI_JOB_NAME
when: never
# Like variable_filters, but without forcing: JOB_FILTER can only *select* the
# job, its own conditions still have to match as well. For jobs that must not
# become startable outside of the pipelines they were written for.
variable_filters_without_force:
- if: $BROWSER_TEST_FILTER
when: never
- if: $JOB_FILTER && $JOB_FILTER != $CI_JOB_NAME
when: never
.job_rules_default:
rules:
- !reference [.job_rules, variable_filters]
- when: on_success
# Closing entry: offer a manual start and never block the pipeline. `allow_failure`
# has to be spelled out for manual jobs - in `rules:` it defaults to `false`,
# which would turn the manual job into a blocking one.
then_manual:
- when: manual
allow_failure: true
# Closing entry for jobs whose signal is wanted regularly, but which are too slow
# or too dependent on third parties to gate every commit: they run in the
# scheduled pipelines and can be started manually everywhere else.
then_manual_unless_scheduled:
- if: $CI_PIPELINE_SOURCE == "schedule"
when: on_success
- when: manual
allow_failure: true
# Closing entry for secondary test lanes that cover the same specs as a primary
# lane (e.g. Firefox next to Chrome): in merge requests they are advisory and
# only started on demand, on branches they run automatically.
then_manual_in_mr:
.job_rules_manual_in_mr:
rules:
- !reference [.job_rules, variable_filters]
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: manual
allow_failure: true
- when: on_success
# Closing entry for the capybara:filtered:* jobs, which exist only to loop a
# single spec selected with BROWSER_TEST_FILTER. JOB_FILTER is deliberately not
# honoured here, BROWSER_TEST_FILTER is the selector for these jobs.
then_browser_test_filter_only:
- if: $BROWSER_TEST_FILTER
when: always
- when: never
# The one and only rule set: for jobs that just have to honour the pipeline filters.
# Everything else writes its own `rules:`, see the header.
.job_rules_default:
.job_rules_integration:
rules:
- !reference [.rule_fragments, variable_filters]
- !reference [.job_rules, variable_filters]
- if: $CI_COMMIT_REF_NAME =~ /^private/
allow_failure: true
- when: on_success

View File

@@ -32,12 +32,7 @@
# gem and the package.json pin; the driver asserts this at startup
# (spec/support/capybara/playwright_driver.rb), so a gem bump fails fast
# here instead of at the server's version handshake.
PLAYWRIGHT_VERSION: '1.63.0'
.variables_playwright_firefox:
extends: .variables_playwright
variables:
SELENIUM_BROWSER: 'playwright_firefox'
PLAYWRIGHT_VERSION: '1.62.1'
.variables_proxy:
variables:
@@ -89,13 +84,14 @@
alias: selenium-firefox
playwright:
name: $CI_REGISTRY/docker/zammad-playwright:latest
name: $CI_REGISTRY/docker/zammad-playwright:stable
alias: playwright
variables:
# Lets the server accept the Chromium args the driver passes via the
# endpoint's launch-options (see playwright_driver.rb); it strips them
# otherwise. Opt-in because it also unlocks executablePath - fine here,
# as the server is only reachable from this job.
# as the server is only reachable from this job. See
# docker/zammad-playwright!2.
PLAYWRIGHT_UNSAFE: '1'
redis:

View File

@@ -1,16 +0,0 @@
# For the jobs writing JUnit reports (see CI_JUNIT_REPORT_DIR): upload the artifacts of
# passed jobs, too, so that the test reports of pipelines and merge requests contain
# all tests and not only the failed ones. The default after_script removes the logs and
# screenshots of passed jobs, so that only the report of these is uploaded.
.artifacts_with_test_report:
artifacts:
expire_in: 1 week
when: always
paths:
- tmp/screenshot*
- tmp/screenshots/*
# Recorded by spec/support/capybara/playwright_traces.rb in the Playwright lanes.
- tmp/playwright-traces/*
- log/*.log
reports:
junit: tmp/junit/*.xml

View File

@@ -29,16 +29,6 @@ variables:
# Record the runtime of each test file, used to keep .gitlab/ci/timings/ up to date.
CI_TEST_TIMINGS_PATH: 'tmp/test_timings/timings.yml'
# Write JUnit reports for the test reports of GitLab, see spec/support/junit_report.rb and
# .artifacts_with_test_report.
CI_JUNIT_REPORT_DIR: 'tmp/junit'
# Branches that are only ever built through their merge request: feature branches of
# developers (private/*) and branches created by automation (cherry-picks, reverts,
# dependency updates). Used in rules with `=~`, which requires the pattern to be
# enclosed in forward slashes.
CI_ZAMMAD_MR_ONLY_BRANCH_PATTERN: '/^(private|cherry-pick-|revert-|renovate|dependabot)/'
# Compile assets only once.
CI_SKIP_ASSETS_PRECOMPILE: 'true'
# Avoid unnecessary DB resets.

View File

@@ -9,6 +9,6 @@ workflow:
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push"
when: never
# Don't create branch pipelines for pushes to private branches (as there will also be a MR for them).
- if: $CI_COMMIT_BRANCH =~ $CI_ZAMMAD_MR_ONLY_BRANCH_PATTERN && $CI_PIPELINE_SOURCE == "push"
- if: $CI_COMMIT_BRANCH =~ /^(private|cherry-pick-|revert-|renovate|dependabot)/ && $CI_PIPELINE_SOURCE == "push"
when: never
- when: always

View File

@@ -3,12 +3,10 @@
image:
name: alpine/curl:latest
entrypoint: ['']
cache: []
rules:
- !reference [.rule_fragments, main_repository_only]
# Don't apply job filter here.
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /weblate-merge$/ && $CI_MERGE_REQUEST_SQUASH_ON_MERGE == "true"'
when: on_success
- when: never
before_script: []
script:
- echo "⚠️ Disabling squash option for this MR..."
- |
@@ -22,7 +20,6 @@
'push translations update commit':
stage: automate
rules:
- !reference [.rule_fragments, main_repository_only]
# Don't apply job filter here.
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_COMMIT_MESSAGE =~ /Translations update from translations[.]zammad[.]org/
when: always
@@ -79,12 +76,11 @@
# GITLAB_API_TOKEN - read_api token for the MR query (defaults to RENOVATE_TOKEN)
'dependency digest':
stage: automate
cache: []
rules:
- !reference [.rule_fragments, main_repository_only]
- !reference [.rule_fragments, variable_filters_without_force]
# Runs only when explicitly selected via JOB_FILTER, see the comment above.
- if: $BROWSER_TEST_FILTER
when: never
- if: $JOB_FILTER == $CI_JOB_NAME && ($CI_PIPELINE_SOURCE == "schedule" || $CI_PIPELINE_SOURCE == "web") && $CI_COMMIT_REF_NAME == "develop"
when: on_success
- when: never
variables:
GIT_DEPTH: '500'
@@ -105,12 +101,15 @@
image:
name: ghcr.io/updatecli/updatecli:latest
entrypoint: ['']
cache: []
rules:
- !reference [.rule_fragments, main_repository_only]
- !reference [.rule_fragments, variable_filters_without_force]
- if: $BROWSER_TEST_FILTER
when: never
- if: $JOB_FILTER && $JOB_FILTER != $CI_JOB_NAME
when: never
- if: ($CI_PIPELINE_SOURCE == "schedule" || $CI_PIPELINE_SOURCE == "web") && $CI_COMMIT_REF_NAME == "develop"
when: on_success
- when: never
before_script: []
script:
- updatecli pipeline apply --config .dev/updatecli.yaml --values .dev/updatecli-values-enable-gitlab.yaml
after_script: []

View File

@@ -1,16 +1,8 @@
.template_browser-core_capybara:
stage: browser
extends:
- .template_ruby
- .job_rules_default
- .tags_docker_low_concurrency
- .artifacts_with_test_report
needs:
- !reference [.needs, ruby]
- !reference [.needs, database]
- !reference [.needs, assets]
- !reference [.needs, lint]
timeout: 40 minutes
script:
- !reference [.scripts, zammad_ci_test_prepare]
- bundle exec rspec --require ./spec/rspec_extensions.rb --fail-fast -t ~integration -t ~searchindex spec/system
@@ -27,11 +19,8 @@
.template_browser-core_capybara_firefox:
extends:
- .template_browser-core_capybara
- .job_rules_manual_in_mr
- .variables_selenium_firefox
# Chrome covers the same specs, so the Firefox lane is advisory in merge requests.
rules:
- !reference [.rule_fragments, variable_filters]
- !reference [.rule_fragments, then_manual_in_mr]
services:
- !reference [.services, postgresql]
- !reference [.services, selenium-firefox]
@@ -45,12 +34,13 @@
- !reference [.services, postgresql]
- !reference [.services, playwright]
- !reference [.services, redis]
.template_browser-core_capybara_playwright_firefox:
extends:
- .template_browser-core_capybara
- .variables_playwright_firefox
services:
- !reference [.services, postgresql]
- !reference [.services, playwright]
- !reference [.services, redis]
# A job-level artifacts block replaces the global default one entirely -
# repeat its paths and add the traces recorded by playwright_traces.rb.
artifacts:
expire_in: 1 week
when: on_failure
paths:
- tmp/screenshot*
- tmp/screenshots/*
- tmp/playwright-traces/*
- log/*.log

View File

@@ -8,25 +8,14 @@ capybara:firefox:
- .template_browser-core_capybara_firefox
parallel: 16
# Shadow lane for the Playwright driver evaluation: runs automatically on develop
# and in scheduled pipelines next to capybara:chrome, is only started on demand
# in merge requests, and never gates - allow_failure keeps a red lane from
# blocking the pipeline.
# Shadow lane for the Playwright driver evaluation: runs automatically in every
# pipeline next to capybara:chrome, so each commit yields a comparable pair,
# and never gates - allow_failure keeps a red lane from blocking the pipeline.
capybara:playwright:
extends:
- .template_browser-core_capybara_playwright
rules:
- !reference [.rule_fragments, variable_filters]
- !reference [.rule_fragments, then_manual_in_mr]
allow_failure: true
parallel: 16
# Evaluation lane for Playwright's Firefox, not decided yet whether it stays -
# only started on demand and never gates.
capybara:firefox:playwright:
extends:
- .template_browser-core_capybara_playwright_firefox
# Playwright evaluation is paused - job disabled in CI.
rules:
- !reference [.rule_fragments, variable_filters]
- !reference [.rule_fragments, then_manual]
parallel: 16
- when: never

View File

@@ -1,7 +1,6 @@
.template_browser-core_capybara_es:
extends:
- .template_browser-core_capybara
timeout: 30 minutes
script:
- !reference [.scripts, zammad_ci_test_prepare]
- bundle exec rspec --require ./spec/rspec_extensions.rb --fail-fast -t ~integration -t searchindex spec/system
@@ -20,10 +19,6 @@
extends:
- .template_browser-core_capybara_es
- .variables_selenium_firefox
# Like the non-ES Firefox lane: advisory in merge requests.
rules:
- !reference [.rule_fragments, variable_filters]
- !reference [.rule_fragments, then_manual_in_mr]
services:
- !reference [.services, postgresql]
- !reference [.services, elasticsearch]
@@ -39,6 +34,16 @@
- !reference [.services, elasticsearch]
- !reference [.services, playwright]
- !reference [.services, redis]
# A job-level artifacts block replaces the global default one entirely -
# repeat its paths and add the traces recorded by playwright_traces.rb.
artifacts:
expire_in: 1 week
when: on_failure
paths:
- tmp/screenshot*
- tmp/screenshots/*
- tmp/playwright-traces/*
- log/*.log
capybara:es:chrome:
extends:
@@ -47,6 +52,7 @@ capybara:es:chrome:
capybara:es:firefox:
extends:
- .template_browser-core_capybara_firefox_es
- .job_rules_manual_in_mr
# Shadow lane for the Playwright driver evaluation, twin of capybara:es:chrome:
# the searchindex-tagged system specs are excluded from the main capybara
@@ -54,7 +60,7 @@ capybara:es:firefox:
capybara:es:playwright:
extends:
- .template_browser-core_capybara_playwright_es
rules:
- !reference [.rule_fragments, variable_filters]
- !reference [.rule_fragments, then_manual_in_mr]
allow_failure: true
# Playwright evaluation is paused - job disabled in CI.
rules:
- when: never

View File

@@ -1,11 +1,8 @@
.template_capybara_filtered:
# This template has to be listed *after* the browser template in `extends:`: its
# `rules:` and `script:` deliberately replace the inherited ones (lists are
# replaced, not merged).
rules:
- !reference [.rule_fragments, then_browser_test_filter_only]
# Looping a single spec takes much longer than a regular slice of the suite.
timeout: 2 hours
- if: $BROWSER_TEST_FILTER
when: always
- when: never
variables:
BROWSER_TEST_FILTER_LOOP_COUNT: 50
script:
@@ -44,6 +41,9 @@ capybara:filtered:playwright:
- .template_browser-core_capybara_playwright
- .template_capybara_filtered
- .variables_auth
# Playwright evaluation is paused - job disabled in CI.
rules:
- when: never
services:
- !reference [.template_browser-core_capybara_playwright, services]
# Also add containers for integration tests here.

View File

@@ -1,14 +1,9 @@
capybara:integration:other:
extends:
- .template_browser-core_capybara_chrome
- .job_rules_manual_in_mr
- .variables_idoit
- .variables_proxy
timeout: 20 minutes
# These tests need third party service containers and are slow, so they are
# advisory in merge requests.
rules:
- !reference [.rule_fragments, variable_filters]
- !reference [.rule_fragments, then_manual_in_mr]
services:
- !reference [.services, postgresql]
- !reference [.services, selenium-chrome]
@@ -23,13 +18,8 @@ capybara:integration:other:
capybara:integration:saml:
extends:
- .template_browser-core_capybara_chrome
- .job_rules_manual_in_mr
- .variables_auth
timeout: 20 minutes
# These tests need third party service containers and are slow, so they are
# advisory in merge requests.
rules:
- !reference [.rule_fragments, variable_filters]
- !reference [.rule_fragments, then_manual_in_mr]
services:
- !reference [.services, postgresql]
- !reference [.services, selenium-chrome]
@@ -42,12 +32,7 @@ capybara:integration:saml:
.template_capybara_integration_otrs:
extends:
- .template_browser-core_capybara_chrome
timeout: 20 minutes
# These tests need third party service containers and are slow, so they are
# advisory in merge requests.
rules:
- !reference [.rule_fragments, variable_filters]
- !reference [.rule_fragments, then_manual_in_mr]
- .job_rules_manual_in_mr
variables:
IMPORT_OTRS_ENDPOINT: "http://zammad-ci-otrsimport-app/otrs/public.pl?Action=ZammadMigrator"
services:

View File

@@ -5,10 +5,8 @@
'write database dumps':
stage: lint
extends:
- .template_ruby
- .job_rules_default
timeout: 6 minutes
needs: !reference [.needs, ruby]
services:
- !reference [.services, postgresql]
variables:

View File

@@ -3,18 +3,16 @@
stage: lint
extends:
- .job_rules_default
# Start right away: the caches written in the pre stage only change with the lock
# files, so they are usually filled already.
needs: []
.template_lint_rails:
extends:
- .template_lint
- .template_ruby
services:
- !reference [.services, postgresql]
before_script:
- !reference [.template_ruby, before_script]
- !reference [.scripts, source_rvm]
- !reference [.scripts, bundle_install]
- !reference [.scripts, configure_environment]
- !reference [.scripts, zammad_db_init]
variables:
ZAMMAD_SAFE_MODE: 1
@@ -25,8 +23,10 @@
- .template_lint
timeout: 2 minutes
image: koalaman/shellcheck-alpine:stable
cache: []
before_script: []
script:
- find . \( -name vendor -o -name node_modules \) -prune -o \( -name '*.sh' -o -name functions \) -print0 | xargs -0 shellcheck -S warning
- shellcheck -S warning $(find . -name "*.sh" -o -name "functions" | egrep -v "/vendor|node_modules/")
after_script: []
'lint: i18n & rails':
@@ -49,7 +49,6 @@
'lint: ruby & security':
extends:
- .template_lint
- .template_ruby
timeout: 10 minutes
before_script:
- !reference [.scripts, source_rvm]
@@ -67,10 +66,8 @@
- echo "bundler-audit security check…"
- gem exec bundle-audit update
- gem exec bundle-audit
- echo "Native gem platforms check…"
- .gitlab/check_native_gem_platforms.rb
- echo "Brakeman security check…"
- bin/brakeman -o /dev/stdout -o tmp/brakeman-report.html
- bundle exec brakeman -o /dev/stdout -o tmp/brakeman-report.html
- echo "Checking if yard can generate documentation…"
- bundle exec yard --no-output --no-progress
- echo "Verify that vendored gems are not world writable"
@@ -127,8 +124,8 @@
timeout: 2 minutes
allow_failure: true
rules:
- !reference [.rule_fragments, variable_filters]
- !reference [.rule_fragments, then_manual]
- !reference [.job_rules, variable_filters]
- when: manual
script:
- bundle exec rake zammad:ci:bundle:orphaned 6
@@ -137,8 +134,11 @@
- .template_lint_rails
timeout: 4 minutes
rules:
- !reference [.rule_fragments, variable_filters]
- !reference [.rule_fragments, then_manual_unless_scheduled]
- !reference [.job_rules, variable_filters]
- if: $CI_PIPELINE_SOURCE != "schedule"
when: manual
allow_failure: true
- when: on_success
script:
- bundle exec rake zammad:ci:update_ci_variables
@@ -147,10 +147,12 @@
image:
name: 'zricethezav/gitleaks:latest'
entrypoint: ['']
cache: []
timeout: 2 minutes
variables:
# Go uses all CPUs by default.
GOMAXPROCS: 1
before_script: []
script:
# Deep enough to contain the pipeline's base commit, so only the new commits are scanned.
# Setting GIT_DEPTH for this job has no effect, the checkout keeps the depth of 1.

View File

@@ -1,6 +1,4 @@
# Write CI cache once for faster runs
# No `rules:` on purpose: filtered pipelines ($JOB_FILTER, $BROWSER_TEST_FILTER)
# need the cache, too.
'write cache: ruby':
stage: pre
cache: !reference [.cache, read_write_ruby]
@@ -10,20 +8,20 @@
- !reference [.scripts, bundle_install]
after_script: []
# No `rules:` on purpose, see above.
'write cache: nodejs':
stage: pre
cache: !reference [.cache, read_write_nodejs]
before_script: []
script:
- !reference [.scripts, pnpm_install_all]
after_script: []
'push to github':
stage: pre
cache: []
rules:
- !reference [.rule_fragments, main_repository_only]
# Don't apply job filter here.
- if: $CI_COMMIT_REF_NAME =~ $CI_ZAMMAD_MR_ONLY_BRANCH_PATTERN
- if: $CI_COMMIT_REF_NAME =~ /^(private|cherry-pick-|revert-|renovate|dependabot)/
when: never
- when: always
before_script:
@@ -41,8 +39,8 @@
stage: pre
tags:
- push-to-ftp
cache: []
rules:
- !reference [.rule_fragments, main_repository_only]
# Don't apply job filter here.
- if: $CI_COMMIT_TAG
when: always

View File

@@ -56,7 +56,7 @@
# Build/update docker image and publish it to the GitLab container registry.
- |
echo "Build docker image…"
echo "$CI_JOB_TOKEN" | docker login -u gitlab-ci-token --password-stdin $CI_REGISTRY
docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
echo -e "\\e[0Ksection_start:`date +%s`:docker_build[collapsed=true]\\r\\e[0Kdocker build"
if [ ! "${SKIP_DOCKER_BUILD}" ]
then
@@ -231,6 +231,7 @@
tags:
- review-apps
stage: pre
cache: []
variables:
SERVER_NAME: review-apps.dc.zammad.com
APP_SLUG: $CI_COMMIT_REF_SLUG
@@ -241,10 +242,7 @@
environment:
name: ${APP_NAME}
url: https://${APP_FQDN}#getting_started/auto_wizard
# Deploying, stopping and restarting the same review app must not overlap.
resource_group: ${APP_NAME}
rules:
- !reference [.rule_fragments, main_repository_only]
- if: $CI_MERGE_REQUEST_ID
when: manual
allow_failure: true

View File

@@ -1,13 +1,8 @@
.template_api_client:
stage: test
extends:
- .template_ruby
- .job_rules_default
- .tags_docker_low_concurrency
needs:
- !reference [.needs, ruby]
- !reference [.needs, lint]
timeout: 15 minutes
services:
- !reference [.services, postgresql]
- !reference [.services, redis]
@@ -20,15 +15,10 @@ api_client:php:
extends:
- .template_api_client
variables:
COMPOSER_CACHE_DIR: "$CI_PROJECT_DIR/.cache/composer"
ZAMMAD_PHP_API_CLIENT_UNIT_TESTS_URL: "http://localhost:3000"
ZAMMAD_PHP_API_CLIENT_UNIT_TESTS_USERNAME: "admin@example.com"
ZAMMAD_PHP_API_CLIENT_UNIT_TESTS_PASSWORD: "test"
ZAMMAD_PHP_API_CLIENT_UNIT_TESTS_DEBUG: 0
cache:
key: "composer-api-client-php"
paths:
- .cache/composer/
script:
- RAILS_ENV=test bundle exec rake db:create
- echo -e "\\e[0Ksection_start:`date +%s`:zammad_ci_test_start[collapsed=true]\\r\\e[0Kbundle exec rake zammad:ci:test:start"
@@ -45,10 +35,6 @@ api_client:php:
api_client:ruby:
extends:
- .template_api_client
cache:
key: "bundle-api-client-ruby"
paths:
- .cache/bundle/
script:
- RAILS_ENV=test bundle exec rake db:create
- cp contrib/auto_wizard_test.json auto_wizard.json
@@ -57,7 +43,6 @@ api_client:ruby:
- echo -e "\\e[0Ksection_end:`date +%s`:zammad_ci_test_start\\r\\e[0K"
- git clone https://github.com/zammad/zammad-api-client-ruby.git
- cd zammad-api-client-ruby
- export BUNDLE_PATH="$CI_PROJECT_DIR/.cache/bundle"
- bundle update --bundler
- bundle install -j $(nproc)
- bundle exec rspec

View File

@@ -1,16 +1,5 @@
assets:build:
stage: test
needs:
- !reference [.needs, ruby]
- !reference [.needs, nodejs]
- !reference [.needs, lint]
timeout: 15 minutes
# No `rules:` on purpose: the browser jobs of a filtered pipeline ($JOB_FILTER,
# $BROWSER_TEST_FILTER) still need the assets built here, as the specs
# themselves skip the compilation ($CI_SKIP_ASSETS_PRECOMPILE).
extends:
- .template_ruby
- .no_artifact_dependencies
services:
- !reference [.services, postgresql]
artifacts:
@@ -39,14 +28,7 @@ assets:build:
assets:build:pwa:
stage: test
extends:
- .template_ruby
- .job_rules_default
- .no_artifact_dependencies
needs:
- !reference [.needs, ruby]
- !reference [.needs, nodejs]
- !reference [.needs, lint]
timeout: 15 minutes
services:
- !reference [.services, postgresql]
cache: !reference [.cache, read_only_full]

View File

@@ -6,12 +6,11 @@
# The job only needs the repository checkout. Without this, it implicitly
# depends on the artifacts of all earlier stages, so retrying it after those
# artifacts expired fails with `missing_dependency_failure`.
needs:
- !reference [.needs, lint]
timeout: 15 minutes
needs: []
variables:
DOCKER_HOST: unix:///var/run/docker.sock
DEVCONTAINERS_CLI_VERSION: '0.89.0'
DEVCONTAINERS_CLI_VERSION: '0.87.0'
cache: []
before_script:
- apk add --no-cache docker-cli docker-cli-buildx docker-compose
after_script: []
@@ -24,5 +23,8 @@
--config .devcontainer/$VARIANT/devcontainer.json
--frozen-lockfile
rules:
- !reference [.rule_fragments, variable_filters]
- !reference [.rule_fragments, then_manual_unless_scheduled]
- !reference [.job_rules, variable_filters]
- if: $CI_PIPELINE_SOURCE == "schedule"
when: on_success
- when: manual
allow_failure: true

View File

@@ -1,26 +1,21 @@
.template_docker:
image: docker
stage: test
extends:
- .no_artifact_dependencies
needs:
- !reference [.needs, lint]
timeout: 30 minutes
interruptible: false
rules:
- !reference [.rule_fragments, main_repository_only]
- !reference [.rule_fragments, variable_filters]
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_COMMIT_REF_NAME =~ $CI_ZAMMAD_MR_ONLY_BRANCH_PATTERN
- !reference [.job_rules, variable_filters]
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_COMMIT_REF_NAME =~ /^(private|cherry-pick-|revert-|renovate|dependabot)/
when: manual
allow_failure: true
- when: on_success
services: []
cache: []
variables:
ZAMMAD_IMAGE_TAG: "ci-${CI_JOB_ID}-${CI_COMMIT_SHORT_SHA}"
ZAMMAD_IMAGE_REPO: "zammad"
ZAMMAD_IMAGE: "${ZAMMAD_IMAGE_REPO}:${ZAMMAD_IMAGE_TAG}"
before_script:
- echo "$CI_JOB_TOKEN" | docker login -u gitlab-ci-token --password-stdin $CI_REGISTRY
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- git clone --depth=1 https://github.com/zammad/zammad-docker-compose.git
# Make the stack use the image that was just built.
- echo "IMAGE_REPO=${ZAMMAD_IMAGE_REPO}" > zammad-docker-compose/.env

View File

@@ -2,20 +2,13 @@
stage: test
extends:
- .job_rules_default
- .no_artifact_dependencies
- .artifacts_with_test_report
needs:
- !reference [.needs, nodejs]
- !reference [.needs, lint]
timeout: 15 minutes
cache: !reference [.cache, read_only_nodejs]
before_script:
- !reference [.scripts, pnpm_install]
- !reference [.scripts, pnpm_install_eslint_plugin_zammad]
script:
# Only run one worker to avoid load peaks affecting other jobs.
# Vitest resolves a relative output file against app/frontend, its root.
- pnpm test --max-workers=1 --shard $CI_NODE_INDEX/$CI_NODE_TOTAL --reporter=default --reporter=junit --outputFile.junit=$CI_PROJECT_DIR/$CI_JUNIT_REPORT_DIR/vitest.xml
- pnpm test --max-workers=1 --shard $CI_NODE_INDEX/$CI_NODE_TOTAL
# Stop here, except on the first shard.
- "[[ $CI_NODE_INDEX = 1 ]] || exit 0"
- echo "Also test eslint-plugin-zammad…"
@@ -32,11 +25,6 @@ frontend:cypress:
extends:
- .job_rules_default
- .tags_docker_low_concurrency
- .no_artifact_dependencies
needs:
- !reference [.needs, nodejs]
- !reference [.needs, lint]
timeout: 15 minutes
cache: !reference [.cache, read_only_nodejs]
before_script:
- !reference [.scripts, pnpm_install_cypress]

View File

@@ -3,24 +3,10 @@ include:
.template_integration:
extends:
- .template_ruby
- .artifacts_with_test_report
# These tests partly talk to live third party services. On private branches their
# signal is advisory only, so a failure does not block the pipeline.
rules:
- !reference [.rule_fragments, variable_filters]
- if: $CI_COMMIT_REF_NAME =~ /^private/
when: on_success
allow_failure: true
- when: on_success
- .job_rules_integration
services:
- !reference [.services, postgresql]
- !reference [.services, imap]
- !reference [.services, redis]
- !reference [.services, memcached]
stage: test
needs:
- !reference [.needs, ruby]
- !reference [.needs, database]
- !reference [.needs, lint]
timeout: 15 minutes

View File

@@ -1,18 +1,10 @@
.template_integration_es:
extends:
- .template_ruby
- .artifacts_with_test_report
services:
- !reference [.services, postgresql]
- !reference [.services, redis]
- !reference [.services, memcached]
- !reference [.services, elasticsearch]
stage: test
needs:
- !reference [.needs, ruby]
- !reference [.needs, database]
- !reference [.needs, lint]
timeout: 30 minutes
script:
- !reference [.scripts, zammad_db_init]
- bundle exec rspec --tag searchindex --tag ~type:system
@@ -27,8 +19,8 @@ rspec:es:
matrix:
- ELASTICSEARCH_TAG: [stable, latest]
rules:
- !reference [.rule_fragments, variable_filters]
# The lowest supported version is covered in every pipeline.
- !reference [.job_rules, variable_filters]
- if: $ELASTICSEARCH_TAG == "stable"
when: on_success
- !reference [.rule_fragments, then_manual_unless_scheduled]
- if: $CI_PIPELINE_SOURCE == "schedule"
- when: manual
allow_failure: true

View File

@@ -20,8 +20,9 @@
extends:
- .template_integration_otrs
rules:
- !reference [.rule_fragments, variable_filters]
- !reference [.rule_fragments, then_manual]
- !reference [.job_rules, variable_filters]
- when: manual
allow_failure: true
rspec:integration:otrs:6:
extends:

View File

@@ -5,31 +5,26 @@ rspec:migration:
stage: test
extends:
- .job_rules_default
- .artifacts_with_test_report
needs:
- !reference [.needs, ruby]
- !reference [.needs, lint]
timeout: 20 minutes
cache: !reference [.cache, read_only_ruby]
services:
- !reference [.services, postgresql]
- !reference [.services, redis]
# The database must be initialized by the code of the stable branch, so the database dump
# of this pipeline must not be used here.
dependencies: []
before_script: []
script:
# First, checkout stable and set it up.
- git fetch --depth=1 origin stable-6.0
- git checkout FETCH_HEAD
- git fetch --unshallow
- git checkout stable-6.0
- !reference [.scripts, source_rvm]
- rvm use $(cat .ruby-version)
- rvm use 3.1.3
- bundle config set without mysql
- !reference [.scripts, bundle_install]
- !reference [.scripts, configure_environment]
- !reference [.scripts, zammad_db_init]
# Then, switch to the current commit, migrate to it and run a few selected tests.
- git checkout $CI_COMMIT_SHA
- rvm use $(cat .ruby-version)
- rvm use 3.4.9
- !reference [.scripts, bundle_install]
# Force redis usage, even if it was disabled by the initial configure_environment script of stable.
- export REDIS_URL=redis://redis

View File

@@ -1,24 +1,16 @@
.packager.io:
image: docker
stage: test
extends:
- .no_artifact_dependencies
needs:
- !reference [.needs, lint]
timeout: 30 minutes
interruptible: false
allow_failure: true
retry: 2
rules:
- !reference [.rule_fragments, main_repository_only]
# Don't apply job filter here.
# These jobs can only run for a) published paths and b) when triggered after the packager.io build is complete.
# The pattern is deliberately not anchored at the end: packager.io publishes a
# repository per branch, so stable-* maintenance branches are valid targets, too.
- if: ($CI_PIPELINE_SOURCE == "schedule" || $CI_PIPELINE_SOURCE == "web") && $CI_COMMIT_REF_NAME =~ /^(stable|develop)/
when: on_success
- when: never
services: []
cache: []
before_script: []
script:
- .gitlab/packager.io/run_scenarios.sh
after_script:
@@ -28,4 +20,4 @@ packager.io:
extends: [.packager.io]
parallel:
matrix:
- DISTRO: [debian-12, debian-13, ubuntu-22.04, ubuntu-24.04, ubuntu-26.04, rhel-9, rhel-10, sles-15, sles-16.0]
- DISTRO: [debian-11, debian-12, debian-13, ubuntu-22.04, ubuntu-24.04, ubuntu-26.04, rhel-9, rhel-10, sles-15, sles-16.0]

View File

@@ -1,35 +1,29 @@
performance:
stage: test
needs:
- !reference [.needs, lint]
timeout: 45 minutes
rules:
- !reference [.rule_fragments, variable_filters]
- !reference [.job_rules, variable_filters]
- if: $CI_PIPELINE_SOURCE == "schedule" || $CI_PIPELINE_SOURCE == "web"
when: on_success
allow_failure: true
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- .gitlab/ci/test/performance.yml
- .gitlab/performance/**/*
when: on_success
allow_failure: true
# No manual start: the job runs for a long time and needs an exclusive runner.
- when: never
# Make sure this gets executed exclusively on a runner.
tags: ['no_concurrency']
services:
- !reference [.services, postgresql]
- !reference [.services, redis]
cache: []
before_script:
# Copy performance checking code to tmp/ so that it survives branch switching
- cp -rv .gitlab/performance tmp/
# First, checkout stable and set it up.
- git fetch --depth=1 origin stable-6.0
- git checkout FETCH_HEAD
- git fetch --unshallow >/dev/null 2>&1
- git checkout stable-6.0
- !reference [.scripts, source_rvm]
- rvm use $(cat .ruby-version)
- rvm use 3.1.3
- bundle config set without mysql
- !reference [.scripts, bundle_install]
- !reference [.scripts, configure_environment]
@@ -41,7 +35,7 @@ performance:
- bundle exec rails r tmp/performance/run_tests.rb || true
# Then, switch to the current commit, migrate to it and run a few selected tests.
- git checkout $CI_COMMIT_SHA
- rvm use $(cat .ruby-version)
- rvm use 3.4.9
- !reference [.scripts, bundle_install]
# Force redis usage, even if it was disabled by the initial configure_environment script of stable.
- export REDIS_URL=redis://redis

View File

@@ -4,14 +4,7 @@ include:
.template_rspec:
stage: test
extends:
- .template_ruby
- .job_rules_default
- .artifacts_with_test_report
needs:
- !reference [.needs, ruby]
- !reference [.needs, database]
- !reference [.needs, lint]
timeout: 40 minutes
script:
- !reference [.scripts, zammad_db_init]
- bundle exec rspec --require ./spec/rspec_extensions.rb --exclude-pattern "spec/system/**/*_spec.rb" -t ~searchindex -t ~integration
@@ -26,28 +19,13 @@ include:
rspec:integration:
stage: test
extends:
- .template_ruby
- .variables_proxy
- .variables_mattermost
- .variables_s3
- .artifacts_with_test_report
needs:
- !reference [.needs, ruby]
- !reference [.needs, database]
- !reference [.needs, lint]
timeout: 30 minutes
allow_failure: true
# Squid permits CONNECT to 443, 8443 and 3001 only (configs/squid-ci.conf in
# docker/zammad-proxies), so the SSL server of spec/lib/user_agent_spec.rb
# has to stay on 3001 for the examples that go through the proxy service.
# Safe here, and only here: this is the one job that runs that spec, and it
# excludes spec/system, so Capybara never serves on that port alongside it.
variables:
ZAMMAD_TEST_WEB_SSL_PORT: '3001'
rules:
- !reference [.rule_fragments, variable_filters]
- !reference [.job_rules, variable_filters]
- if: $CI_PIPELINE_SOURCE == "schedule"
when: on_success
allow_failure: true
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
@@ -89,9 +67,9 @@ rspec:integration:
- app/models/{channel,external_credential,store,webhook}.rb
- app/models/{channel,external_credential,store,webhook}/**/*
- app/services/service/{ai,ticket/external_references}/**/*
when: on_success
allow_failure: true
- !reference [.rule_fragments, then_manual]
- when: manual
allow_failure: true
services:
- !reference [.services, postgresql]
- !reference [.services, imap]

View File

@@ -2,21 +2,14 @@
addon:subscription:
stage: test
extends:
- .template_ruby
- .artifacts_with_test_report
needs:
- !reference [.needs, ruby]
- !reference [.needs, database]
- !reference [.needs, lint]
timeout: 15 minutes
- .job_rules_default
rules:
- !reference [.rule_fragments, main_repository_only]
- !reference [.rule_fragments, variable_filters]
- !reference [.job_rules, variable_filters]
- if: $CI_COMMIT_REF_NAME == 'develop'
when: on_success
# Complain if breaking changes were merged and subscription is not ported.
allow_failure: false
- if: $CI_COMMIT_REF_NAME =~ /^private/
- if: $CI_COMMIT_REF_NAME =~ /^(private|develop)/
when: on_success
# It could happen that in a branch we need to make breaking changes, so
# only hint at the situation but don't block the pipeline.

View File

@@ -1,12 +1,5 @@
.template_supabase:
stage: test
extends:
- .template_ruby
- .no_artifact_dependencies
needs:
- !reference [.needs, ruby]
- !reference [.needs, lint]
timeout: 10 minutes
tags:
- push-to-ftp # use only allow-listed ci runners
services:
@@ -18,10 +11,9 @@ supabase:translation_stats:
extends:
- .template_supabase
rules:
- !reference [.rule_fragments, main_repository_only]
- if: $CI_PIPELINE_SOURCE == "schedule" && $CI_COMMIT_REF_NAME =~ /^(develop|stable)$/
when: always
- when: never
- if: $CI_PIPELINE_SOURCE == "schedule" && $CI_COMMIT_REF_NAME =~ /^(develop|stable)$/
when: always
- when: never
script:
- bundle exec rails runner .gitlab/supabase/translation_stats.rb
@@ -29,7 +21,6 @@ supabase:github_stats:
extends:
- .template_supabase
rules:
- !reference [.rule_fragments, main_repository_only]
- if: $CI_PIPELINE_SOURCE == "schedule" && $CI_COMMIT_REF_NAME == "develop"
when: always
- when: never

19
.gitlab/environment_info.sh Executable file
View File

@@ -0,0 +1,19 @@
#!/bin/bash
date -u --rfc-3339=seconds
echo
top -b -n1 -w240
echo
ps aux --width 240
echo
free -m
echo
mount
echo
df -h
echo
echo "Last 100 lines from kernel log"
dmesg | tail -n 100
echo
echo "Last 1000 lines from system log (journalctl)"
journalctl -n 1000

View File

@@ -6,6 +6,11 @@ export DEBIAN_FRONTEND=noninteractive
cd "$(dirname $0)"
# Debian 11 LTS has ended and its security updates were removed from deb.debian.org, but not yet added to archive.debian.org.
if [ "${DISTRIBUTION_VERSION}" = "11" ]; then
sed -i 's#^deb http://deb.debian.org/debian-security bullseye-security main$#deb [check-valid-until=no] http://snapshot.debian.org/archive/debian-security/20260830T000000Z bullseye-security main#' /etc/apt/sources.list
fi
bash ./shared.deb.sh
echo "deb [signed-by=/usr/share/keyrings/zammad-archive-keyring.gpg] https://go.packager.io/srv/deb/zammad/zammad/${CI_COMMIT_REF_NAME}/debian ${DISTRIBUTION_VERSION} main" > /etc/apt/sources.list.d/zammad.list

View File

@@ -6,9 +6,6 @@ dnf update -y
dnf install -y systemd epel-release
# A background metadata refresh races with the scenario's dnf calls on the lock file.
systemctl mask dnf-makecache.timer dnf-makecache.service
rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch
echo "[elasticsearch-8.x]
name=Elasticsearch repository for 8.x packages

View File

@@ -2,21 +2,6 @@
set -eu
# systemd is not reachable yet right after container start, and reports "degraded" inside
# containers once boot has finished.
booted=false
for _ in $(seq 60); do
case "$(systemctl is-system-running 2>/dev/null || true)" in
running|degraded) booted=true; break ;;
esac
sleep 1
done
if [ "$booted" != true ]; then
echo "systemd did not finish booting within 60 seconds." >&2
exit 1
fi
dnf install -y zammad
curl --retry 30 --retry-delay 1 --retry-connrefused http://localhost:3000 | grep "Zammad Helpdesk"

View File

@@ -1,16 +1,5 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"ignorePaths": ["**/node_modules/**"],
"enabledManagers": [
"bundler",
"npm",
"github-actions",
"devcontainer",
"dockerfile",
"docker-compose",
"gitlabci",
"custom.regex"
],
"enabledManagers": ["bundler", "npm", "github-actions", "devcontainer", "custom.regex"],
"customManagers": [
{
"customType": "regex",
@@ -21,15 +10,6 @@
"depNameTemplate": "@devcontainers/cli",
"datasourceTemplate": "npm"
},
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitlab/ci/.*\\.yml$/"
],
"matchStrings": ["PLAYWRIGHT_VERSION: '(?<currentValue>[^']+)'"],
"depNameTemplate": "playwright",
"datasourceTemplate": "npm"
},
{
"customType": "regex",
"managerFilePatterns": [
@@ -47,11 +27,6 @@
"commitMessageExtra": "(Ruby)"
}
},
"gitlabci": {
"managerFilePatterns": [
"/^\\.gitlab/ci/.*\\.yml$/"
]
},
"devcontainer": {
"managerFilePatterns": [
"/^\\.devcontainer/[^/]+/devcontainer\\.json$/"
@@ -66,11 +41,6 @@
}
},
"minimumReleaseAge": "3 days",
"osvVulnerabilityAlerts": true,
"vulnerabilityAlerts": {
"minimumReleaseAge": null,
"addLabels": ["security"]
},
"assignees": ["fl"],
"packageRules": [
{
@@ -95,35 +65,8 @@
},
{
"matchCategories": ["js"],
"matchFileNames": [".dev/cypress/package.json"],
"addLabels": ["cypress"]
},
{
"matchManagers": ["dockerfile", "docker-compose"],
"matchFileNames": [
".dev/**",
".gitlab/packager.io/**"
],
"enabled": false
},
{
"matchPackageNames": [
"playwright",
"playwright-ruby-client",
"capybara-playwright-driver"
],
"groupName": "Playwright packages",
"groupSlug": "playwright"
},
{
"matchDepNames": ["ruby", "docker.io/library/ruby"],
"groupName": "Ruby version",
"groupSlug": "ruby-version"
},
{
"matchDepNames": ["node", "docker.io/library/node"],
"groupName": "Node version",
"groupSlug": "node-version"
"matchFileNames": [".cypress/package.json"],
"labels": ["cypress"]
},
{
"matchCategories": ["js"],
@@ -131,12 +74,6 @@
"groupSlug": "eslint",
"matchPackageNames": ["/eslint/"]
},
{
"matchCategories": ["js"],
"groupName": "Oxc packages",
"groupSlug": "oxc",
"matchPackageNames": ["oxlint", "oxfmt", "eslint-plugin-oxlint"]
},
{
"matchCategories": ["js"],
"groupName": "Stylelint packages",
@@ -181,8 +118,7 @@
"/^@vue/",
"/^vue/",
"!@vue/eslint-config-typescript",
"!@vuepic/vue-datepicker",
"!/^@vueuse/"
"!@vuepic/vue-datepicker"
],
"groupName": "Vue packages",
"groupSlug": "vue"

View File

@@ -2,7 +2,24 @@
"semi": false,
"singleQuote": true,
"printWidth": 100,
"sortTailwindcss": {
"stylesheet": "./app/frontend/apps/desktop/styles/main.css"
},
"sortPackageJson": false,
"overrides": [
{
"files": ["./app/frontend/apps/desktop/**"],
"options": {
"sortTailwindcss": { "stylesheet": "./app/frontend/apps/desktop/styles/main.css" }
}
},
{
"files": ["./app/frontend/apps/mobile/**"],
"options": {
"sortTailwindcss": { "stylesheet": "./app/frontend/apps/mobile/styles/main.css" }
}
}
],
"ignorePatterns": [
"app/frontend/**/graphql/**/*.ts",
"!app/frontend/tests/graphql/**/*.ts",

View File

@@ -35,6 +35,20 @@ targets:
# EPEL packages can only be installed after enabling the EPEL repo (see before.sh).
- epel-release
- shared-mime-info
debian-11:
dependencies:
- curl
- elasticsearch|elasticsearch-oss
- nginx|apache2
- postgresql
- libimlib2
- libjemalloc2
- shared-mime-info
- redis-server
build_dependencies:
- libimlib2
- libimlib2-dev
- shared-mime-info
debian-12:
dependencies:
- curl

View File

@@ -2,32 +2,6 @@
## Upcoming releases
### `String#utf8_encode` and `Object#to_utf8` will be removed
**Who is affected?** Developers of third-party packages, patches or add-ons that still call
`String#utf8_encode` or `Object#to_utf8`.
Both core extensions, deprecated since Zammad 7.3, will be removed in Zammad 8.0. Code still calling
them will then raise `NoMethodError`.
⚠️ Switch such code to `TextEncoding.utf8_encode` before updating to Zammad 8.0.
**Related issue:** [#6371](https://github.com/zammad/zammad/issues/6371)
### Debian 11 will no longer be supported
**Who is affected?** Admins of instances installed from the Zammad package repository on Debian 11
("bullseye").
Debian 11 reached its LTS end of life on 2026-08-31 and no longer receives security updates. A future
release will therefore no longer be built for it, and the package repository will provide no Debian 11
packages for that release and later ones. Zammad 7.1 keeps receiving Debian 11 packages until it goes
out of maintenance.
⚠️ Upgrade the operating system to a supported Debian release before updating.
**Related issue:** [#6360](https://github.com/zammad/zammad/issues/6360)
### Inline attachments will be listed separately in ticket article API responses
**Who is affected?** Integrations that read inline images from the `attachments` list of ticket article
@@ -41,6 +15,32 @@ will be returned in a dedicated `inline_attachments` key instead.
**Related issue:** [#6254](https://github.com/zammad/zammad/issues/6254)
### Deleting organizations via API will require the `admin.organization` permission
**Who is affected?** Integrations that call `DELETE /api/v1/organizations/:id` with a token that only
has the `ticket.agent` permission.
The endpoint has historically been permitted for users with only the `ticket.agent` permission. That is
deprecated and will be restricted to `admin.organization` in a future release; agent tokens will then
receive `403 Forbidden`.
⚠️ Switch such integrations to an account with the `admin.organization` permission.
**Related issue:** [#6315](https://github.com/zammad/zammad/issues/6315)
### Import mode will also enable maintenance mode
**Who is affected?** Admins of instances that are left in import mode while users are expected to work
in them.
Import mode was never intended for productive environments. In a future release, an instance running in
import mode will automatically run in maintenance mode as well, which prevents non-admin users from
logging in.
⚠️ Disable import mode before letting users work in the instance.
**Related issue:** [#6237](https://github.com/zammad/zammad/issues/6237)
### `Exceptions::UnprocessableEntity` will be removed
**Who is affected?** Developers of custom packages, patches or add-ons that still reference
@@ -71,86 +71,6 @@ instead.
**Related issue:** [#5580](https://github.com/zammad/zammad/issues/5580)
## 7.3
### `String#utf8_encode` and `Object#to_utf8` were deprecated in favour of `TextEncoding.utf8_encode`
**Who is affected?** Developers of third-party packages, patches or add-ons that call
`String#utf8_encode`, `String#utf8_encode!` or `Object#to_utf8`.
Encoding resolution moved out of `lib/core_ext/string.rb` into a dedicated class, `TextEncoding`,
which takes the string as an argument: `string.utf8_encode(from: 'iso-8859-1')` became
`TextEncoding.utf8_encode(string, from: 'iso-8859-1')`, and `object.to_utf8` became
`TextEncoding.utf8_encode(object.to_s)`. `String` no longer requires the `rchardet` and `mail` gems.
`String#utf8_encode` and `Object#to_utf8` still work and now emit a deprecation warning.
`String#utf8_encode!` was removed without a deprecation period, since `TextEncoding.utf8_encode`
never modifies its argument and always returns a new string.
⚠️ Replace calls to `String#utf8_encode` and `Object#to_utf8` with `TextEncoding.utf8_encode`, and
replace `String#utf8_encode!` with an assignment from it.
**Related issue:** [#6371](https://github.com/zammad/zammad/issues/6371)
### Import mode also enables maintenance mode
**Who is affected?** Admins of instances that are left in import mode while users are expected to work
in them.
Import mode was never intended for productive environments. An instance running in import mode now
automatically runs in maintenance mode as well: while the `import_mode` setting is enabled, every
authenticated request of a user without the `admin.maintenance` permission is refused. REST and API
requests get `403 Forbidden`, and GraphQL treats the session as logged out. This covers new logins,
sessions that were already open, and API access via token or HTTP basic authentication alike. An
impersonated session is only exempt when the administrator who switched into it holds
`admin.maintenance`; this also applies to the classic maintenance mode.
⚠️ Disable import mode before letting users work in the instance.
**Related issue:** [#6237](https://github.com/zammad/zammad/issues/6237)
### The knowledge base search index was switched from HTML-escaped text to plain text
**Who is affected?** Admins of instances with Elasticsearch enabled that use the knowledge base, and
integrations that call `POST /api/v1/knowledge_bases/search`.
Knowledge base titles and answer bodies were written into the search index HTML-escaped, so a
category named `Warranty & Returns` was indexed as `Warranty &amp; Returns`. The escape sequences
were searchable in their own right: `amp`, `lt`, `gt` and `nbsp` matched content that contains no
such word, and a search matching one of them, the wildcard `*` among them, highlighted it and left
the sequence visible in the result list ([#6369](https://github.com/zammad/zammad/issues/6369)).
Both texts are now indexed as plain text. Markup in a title is still dropped on its way into the
index, and the title columns themselves keep what was written to them.
The details returned by `POST /api/v1/knowledge_bases/search` changed with it: `title`, `body` and
`subtitle` are now always HTML, with every character escaped and, when `highlight_enabled` is on,
`<em>` marking the matched runs. Previously only a highlighted fragment was guaranteed to look like
this. A detail without a match in its field came back the way its fallback produced it: HTML-escaped
for the answer `body`, the category and knowledge base `title` and the `subtitle`, and as written
for the answer `title`, with highlighting on or off.
The `result` list no longer carries the `highlight` key of each hit. The raw fragments it held mark
their matches with characters that only the `details` rendering above knows how to interpret.
⚠️ Integrations that read the answer `title` as text have to decode it as HTML, like every other detail.
⚠️ Rebuild the search index after the update: `zammad run rake zammad:searchindex:rebuild`
**Related issue:** [#6399](https://github.com/zammad/zammad/issues/6399)
### Deleting organizations via API requires the `admin.organization` permission
**Who is affected?** Integrations that call `DELETE /api/v1/organizations/:id` with a token that only
has the `ticket.agent` permission.
The endpoint has historically been permitted for users with only the `ticket.agent` permission, which
contradicted the API documentation. That behaviour was deprecated in Zammad 7.2: the endpoint now
requires `admin.organization`, and agent tokens receive `403 Forbidden`.
⚠️ Switch such integrations to an account with the `admin.organization` permission.
**Related issue:** [#6315](https://github.com/zammad/zammad/issues/6315)
## 7.2
### Verifying a signup email address no longer signs the user in

View File

@@ -1,8 +1,8 @@
# Change Log for Zammad 7.3.x
# Change Log for Zammad 7.2.1
- [Release notes](https://zammad.com/en/product/releases/zammad-7-3-x)
- [Breaking changes](BREAKING_CHANGES.md#73)
- [Implemented enhancements](https://github.com/zammad/zammad/issues?q=is%3Aclosed+milestone%3A7.3+(-type%3ABug+AND+-label%3Abug))
- [Closed bugs](https://github.com/zammad/zammad/issues?q=is%3Aclosed+milestone%3A7.3+(type%3ABug+OR+label%3Abug))
- [Full commit log](https://github.com/zammad/zammad/compare/7.2.0...7.3.x)
- [File tree](https://github.com/zammad/zammad/tree/7.3.x)
- [Release notes](https://zammad.com/en/product/releases/7-2-1)
- [Breaking changes](BREAKING_CHANGES.md#72)
- [Implemented enhancements](https://github.com/zammad/zammad/issues?q=is%3Aclosed+milestone%3A7.2+(-type%3ABug+AND+-label%3Abug))
- [Closed bugs](https://github.com/zammad/zammad/issues?q=is%3Aclosed+milestone%3A7.2+(type%3ABug+OR+label%3Abug))
- [Full commit log](https://github.com/zammad/zammad/compare/7.2.0...7.2.1)
- [File tree](https://github.com/zammad/zammad/tree/7.2.1)

View File

@@ -18,7 +18,7 @@ WORKDIR /opt/zammad
RUN apt-get update -qq && \
apt-get install -y postgresql-common && \
/usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y && \
apt-get install -y --no-install-recommends libjemalloc2 curl libimlib2 nginx gnupg postgresql-client && \
apt-get install -y --no-install-recommends libjemalloc2 curl libimlib2 libpq5 nginx gnupg postgresql-client && \
apt-get remove -y --purge exim4-base exim4-config bsd-mailx && \
apt-get autoremove -y --purge && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
@@ -48,7 +48,7 @@ SHELL ["/bin/bash", "-o", "errexit", "-o", "pipefail", "-c"]
# Install packages needed to build gems and node modules
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential git libimlib2-dev libssl-dev libyaml-dev && \
apt-get install --no-install-recommends -y build-essential git libimlib2-dev libpq-dev libyaml-dev && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Install application gems

12
Gemfile
View File

@@ -4,7 +4,7 @@ source 'https://rubygems.org'
# core - base
ruby '3.4.9'
gem 'rails', '~> 8.1.0'
gem 'rails', '~> 8.0.0'
gem 'rake'
# core - rails additions
@@ -21,7 +21,6 @@ gem 'puma', group: :puma
gem 'pg', '~> 1.5', '>= 1.5.4', group: :postgres
# core - asynchrous task execution
gem 'benchmark' # undeclared delayed_job dependency
gem 'daemons'
gem 'delayed_job', git: 'https://github.com/zammad-deps/delayed_job', branch: 'threadsafe'
gem 'delayed_job_active_record', git: 'https://github.com/zammad-deps/delayed_job_active_record', branch: 'update-limit-143'
@@ -217,7 +216,6 @@ gem 'elasticsearch', '8.11.2', require: false
group :development, :test do
# test frameworks
gem 'rspec_junit_formatter'
gem 'rspec-rails'
gem 'rspec-retry'
gem 'shoulda-matchers'
@@ -229,11 +227,8 @@ group :development, :test do
gem 'capybara'
gem 'selenium-webdriver'
# Playwright pilot (throwaway evaluation). playwright-ruby-client is pulled in
# by the driver; it is declared here so Renovate sees it and can keep it in
# lockstep with the playwright npm package.
# Playwright pilot (throwaway evaluation)
gem 'capybara-playwright-driver'
gem 'playwright-ruby-client'
# code QA
gem 'brakeman', require: false
@@ -273,6 +268,9 @@ group :development, :test do
# Self-signed localhost certificates for puma / capybara.
gem 'localhost'
# Keycloak admin tool for setting up SAML auth tests
gem 'ruby-keycloak-admin'
# Debugging and profiling
gem 'pry-doc', require: false # This gem is very large, so don't include it in production.

View File

@@ -32,31 +32,29 @@ GEM
simple_po_parser (~> 1.1.6)
aasm (6.0.0)
concurrent-ruby (~> 1.0)
action_text-trix (2.1.19)
railties
actioncable (8.1.4)
actionpack (= 8.1.4)
activesupport (= 8.1.4)
actioncable (8.0.5.1)
actionpack (= 8.0.5.1)
activesupport (= 8.0.5.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (8.1.4)
actionpack (= 8.1.4)
activejob (= 8.1.4)
activerecord (= 8.1.4)
activestorage (= 8.1.4)
activesupport (= 8.1.4)
actionmailbox (8.0.5.1)
actionpack (= 8.0.5.1)
activejob (= 8.0.5.1)
activerecord (= 8.0.5.1)
activestorage (= 8.0.5.1)
activesupport (= 8.0.5.1)
mail (>= 2.8.0)
actionmailer (8.1.4)
actionpack (= 8.1.4)
actionview (= 8.1.4)
activejob (= 8.1.4)
activesupport (= 8.1.4)
actionmailer (8.0.5.1)
actionpack (= 8.0.5.1)
actionview (= 8.0.5.1)
activejob (= 8.0.5.1)
activesupport (= 8.0.5.1)
mail (>= 2.8.0)
rails-dom-testing (~> 2.2)
actionpack (8.1.4)
actionview (= 8.1.4)
activesupport (= 8.1.4)
actionpack (8.0.5.1)
actionview (= 8.0.5.1)
activesupport (= 8.0.5.1)
nokogiri (>= 1.8.5)
rack (>= 2.2.4)
rack-session (>= 1.0.1)
@@ -64,28 +62,27 @@ GEM
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
useragent (~> 0.16)
actiontext (8.1.4)
action_text-trix (~> 2.1.15)
actionpack (= 8.1.4)
activerecord (= 8.1.4)
activestorage (= 8.1.4)
activesupport (= 8.1.4)
actiontext (8.0.5.1)
actionpack (= 8.0.5.1)
activerecord (= 8.0.5.1)
activestorage (= 8.0.5.1)
activesupport (= 8.0.5.1)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (8.1.4)
activesupport (= 8.1.4)
actionview (8.0.5.1)
activesupport (= 8.0.5.1)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
activejob (8.1.4)
activesupport (= 8.1.4)
activejob (8.0.5.1)
activesupport (= 8.0.5.1)
globalid (>= 0.3.6)
activemodel (8.1.4)
activesupport (= 8.1.4)
activerecord (8.1.4)
activemodel (= 8.1.4)
activesupport (= 8.1.4)
activemodel (8.0.5.1)
activesupport (= 8.0.5.1)
activerecord (8.0.5.1)
activemodel (= 8.0.5.1)
activesupport (= 8.0.5.1)
timeout (>= 0.4.0)
activerecord-import (2.3.0)
activerecord (>= 4.2)
@@ -95,20 +92,20 @@ GEM
cgi (>= 0.3.6)
rack (>= 2.0.8, < 4)
railties (>= 7.1)
activestorage (8.1.4)
actionpack (= 8.1.4)
activejob (= 8.1.4)
activerecord (= 8.1.4)
activesupport (= 8.1.4)
activestorage (8.0.5.1)
actionpack (= 8.0.5.1)
activejob (= 8.0.5.1)
activerecord (= 8.0.5.1)
activesupport (= 8.0.5.1)
marcel (~> 1.0)
activesupport (8.1.4)
activesupport (8.0.5.1)
base64
benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
json
logger (>= 1.4.2)
minitest (>= 5.1)
securerandom (>= 0.3)
@@ -133,8 +130,8 @@ GEM
autoprefixer-rails (10.4.21.0)
execjs (~> 2)
aws-eventstream (1.4.0)
aws-partitions (1.1292.0)
aws-sdk-core (3.257.0)
aws-partitions (1.1282.0)
aws-sdk-core (3.254.1)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
@@ -142,12 +139,11 @@ GEM
bigdecimal
jmespath (~> 1, >= 1.6.1)
logger
rexml (~> 3.4, >= 3.4.2)
aws-sdk-kms (1.132.0)
aws-sdk-core (~> 3, >= 3.256.0)
aws-sdk-kms (1.130.0)
aws-sdk-core (~> 3, >= 3.254.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.233.1)
aws-sdk-core (~> 3, >= 3.256.0)
aws-sdk-s3 (1.229.0)
aws-sdk-core (~> 3, >= 3.254.1)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.12.1)
@@ -157,16 +153,16 @@ GEM
samovar (~> 2.1)
base64 (0.3.0)
benchmark (0.5.0)
bigdecimal (4.1.3)
bigdecimal (4.1.2)
bindata (2.5.1)
binding_of_caller (2.0.0)
debug_inspector (>= 1.2.0)
biz (1.8.2)
clavius (~> 1.0)
tzinfo
bootsnap (1.26.0)
bootsnap (1.25.0)
msgpack (~> 1.5)
brakeman (8.1.0)
brakeman (8.0.6)
racc
browser (6.2.0)
builder (3.3.0)
@@ -181,7 +177,7 @@ GEM
rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
capybara-playwright-driver (0.5.12)
capybara-playwright-driver (0.5.10)
addressable
capybara
playwright-ruby-client (>= 1.16.0)
@@ -206,7 +202,7 @@ GEM
coffee-script-source (1.12.2)
concurrent-ruby (1.3.8)
connection_pool (3.0.2)
console (1.38.0)
console (1.37.0)
fiber-annotation
fiber-local (~> 1.1)
json
@@ -219,7 +215,7 @@ GEM
crass (1.0.7)
csv (3.3.6)
daemons (1.4.1)
dalli (5.2.2)
dalli (5.1.1)
logger
dartsass-rails (0.5.1)
railties (>= 6.0.0)
@@ -233,7 +229,7 @@ GEM
activesupport (>= 7.0)
diff-lcs (1.6.2)
diffy (3.4.4)
doorkeeper (5.9.9)
doorkeeper (5.9.6)
railties (>= 5)
drb (2.2.3)
dry-cli (1.4.1)
@@ -290,7 +286,7 @@ GEM
railties (>= 6.1.0)
faker (3.8.0)
i18n (>= 1.8.11, < 2)
faraday (2.14.4)
faraday (2.14.3)
faraday-net_http (>= 2.0, < 3.5)
json
logger
@@ -304,10 +300,6 @@ GEM
faraday-net_http (3.4.4)
net-http (~> 0.5)
ffi (1.17.4)
ffi (1.17.4-aarch64-linux-gnu)
ffi (1.17.4-arm64-darwin)
ffi (1.17.4-x86_64-darwin)
ffi (1.17.4-x86_64-linux-gnu)
ffi-compiler (1.4.2)
ffi (>= 1.15.5)
rake
@@ -319,22 +311,10 @@ GEM
ostruct
globalid (1.4.0)
activesupport (>= 6.1)
google-protobuf (4.36.2)
google-protobuf (4.36.0)
bigdecimal
rake (~> 13.3)
google-protobuf (4.36.2-aarch64-linux-gnu)
bigdecimal
rake (~> 13.3)
google-protobuf (4.36.2-arm64-darwin)
bigdecimal
rake (~> 13.3)
google-protobuf (4.36.2-x86_64-darwin)
bigdecimal
rake (~> 13.3)
google-protobuf (4.36.2-x86_64-linux-gnu)
bigdecimal
rake (~> 13.3)
graphql (2.6.11)
graphql (2.6.10)
base64
fiber-storage
logger
@@ -350,11 +330,15 @@ GEM
redis-client (= 0.30.1)
htmlentities (4.4.2)
http_parser.rb (0.8.1)
httparty (0.24.2)
csv
mini_mime (>= 1.0.0)
multi_xml (>= 0.5.2)
httpclient (2.9.0)
mutex_m
i18n (1.15.2)
concurrent-ruby (~> 1.0)
icalendar (2.12.5)
icalendar (2.12.4)
base64
ice_cube (~> 0.16)
logger
@@ -368,14 +352,14 @@ GEM
inflection (1.0.0)
iniparse (1.5.0)
interception (0.5)
io-console (0.9.4)
io-console (0.9.2)
irb (1.18.0)
pp (>= 0.6.0)
prism (>= 1.3.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jmespath (1.6.2)
json (3.0.2)
json (2.21.2)
json-jwt (1.17.2)
activesupport (>= 4.2)
aes_key_wrap
@@ -383,7 +367,7 @@ GEM
bindata
faraday (~> 2.0)
faraday-follow_redirects
jwt (3.3.0)
jwt (3.2.0)
base64
koala (3.7.0)
addressable
@@ -396,7 +380,7 @@ GEM
rexml
language_server-protocol (3.17.0.6)
lint_roller (1.1.0)
listen (3.10.1)
listen (3.10.0)
logger
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
@@ -426,14 +410,14 @@ GEM
mime-types (3.7.0)
logger
mime-types-data (~> 3.2025, >= 3.2025.0507)
mime-types-data (3.2026.0929)
mime-types-data (3.2026.0701)
mini_mime (1.1.5)
mini_portile2 (2.8.9)
minitest (6.0.6)
drb (~> 2.0)
prism (~> 1.5)
msgpack (1.8.5)
multi_json (1.21.2)
msgpack (1.8.4)
multi_json (1.21.1)
multi_xml (0.9.1)
bigdecimal (>= 3.1, < 5)
multipart-post (2.4.1)
@@ -441,7 +425,7 @@ GEM
nestful (1.1.4)
net-http (0.9.1)
uri (>= 0.11.1)
net-imap (0.6.7)
net-imap (0.6.6)
date
net-protocol
net-ldap (0.20.0)
@@ -449,23 +433,15 @@ GEM
ostruct
net-pop (0.1.2)
net-protocol
net-protocol (0.4.0)
net-protocol (0.3.0)
timeout
net-smtp (0.5.2)
net-smtp (0.5.1)
net-protocol
nio4r (2.7.5)
nkf (0.3.0)
nokogiri (1.19.4)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.19.4-aarch64-linux-gnu)
racc (~> 1.4)
nokogiri (1.19.4-arm64-darwin)
racc (~> 1.4)
nokogiri (1.19.4-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.19.4-x86_64-linux-gnu)
racc (~> 1.4)
nori (2.9.1)
bigdecimal
stringio
@@ -504,8 +480,8 @@ GEM
omniauth-gitlab (4.1.0)
omniauth (~> 2.0)
omniauth-oauth2 (~> 1.8.0)
omniauth-google-oauth2 (1.2.3)
jwt (>= 2.9.2, < 4)
omniauth-google-oauth2 (1.2.2)
jwt (>= 2.9.2)
oauth2 (~> 2.0)
omniauth (~> 2.0)
omniauth-oauth2 (~> 1.8)
@@ -553,20 +529,16 @@ GEM
openssl-signature_algorithm (1.3.0)
openssl (> 2.0)
ostruct (0.6.3)
overcommit (0.73.0)
overcommit (0.72.0)
childprocess (>= 0.6.3, < 6)
iniparse (~> 1.4)
rexml (>= 3.4.2)
parallel (2.3.0)
parallel (2.1.0)
parser (3.3.12.0)
ast (~> 2.4.1)
racc
pg (1.7.0)
pg (1.7.0-aarch64-linux)
pg (1.7.0-arm64-darwin)
pg (1.7.0-x86_64-darwin)
pg (1.7.0-x86_64-linux)
playwright-ruby-client (1.63.0)
pg (1.6.3)
playwright-ruby-client (1.62.0)
base64
concurrent-ruby (>= 1.1.6)
mime-types (>= 3.0)
@@ -620,7 +592,7 @@ GEM
base64 (>= 0.1.0)
logger (>= 1.6.0)
rack (>= 3.0.0, < 4)
rack-proxy (2.0.1)
rack-proxy (1.0.3)
rack (>= 2.0, < 4)
rack-session (2.1.2)
base64 (>= 0.1.0)
@@ -629,20 +601,20 @@ GEM
rack (>= 1.3)
rackup (2.3.1)
rack (>= 3)
rails (8.1.4)
actioncable (= 8.1.4)
actionmailbox (= 8.1.4)
actionmailer (= 8.1.4)
actionpack (= 8.1.4)
actiontext (= 8.1.4)
actionview (= 8.1.4)
activejob (= 8.1.4)
activemodel (= 8.1.4)
activerecord (= 8.1.4)
activestorage (= 8.1.4)
activesupport (= 8.1.4)
rails (8.0.5.1)
actioncable (= 8.0.5.1)
actionmailbox (= 8.0.5.1)
actionmailer (= 8.0.5.1)
actionpack (= 8.0.5.1)
actiontext (= 8.0.5.1)
actionview (= 8.0.5.1)
activejob (= 8.0.5.1)
activemodel (= 8.0.5.1)
activerecord (= 8.0.5.1)
activestorage (= 8.0.5.1)
activesupport (= 8.0.5.1)
bundler (>= 1.15.0)
railties (= 8.1.4)
railties (= 8.0.5.1)
rails-dom-testing (2.3.0)
activesupport (>= 5.0.0)
minitest
@@ -650,9 +622,9 @@ GEM
rails-html-sanitizer (1.7.1)
loofah (~> 2.25, >= 2.25.2)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
railties (8.1.4)
actionpack (= 8.1.4)
activesupport (= 8.1.4)
railties (8.0.5.1)
actionpack (= 8.0.5.1)
activesupport (= 8.0.5.1)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
@@ -669,7 +641,7 @@ GEM
prism (>= 1.6.0)
tsort
rchardet (1.10.2)
rdoc (8.1.0)
rdoc (8.0.0)
erb
prism (>= 1.6.0)
rbs (>= 4.0.0)
@@ -678,7 +650,7 @@ GEM
redis-client (>= 0.22.0)
redis-client (0.30.1)
connection_pool
regexp_parser (2.13.1)
regexp_parser (2.12.0)
reline (0.7.0)
io-console (~> 0.5)
rexml (3.4.4)
@@ -702,10 +674,8 @@ GEM
rspec-retry (0.6.2)
rspec-core (> 3.3)
rspec-support (3.13.7)
rspec_junit_formatter (0.6.0)
rspec-core (>= 2, < 4, != 2.12.0)
rszr (1.5.0)
rubocop (1.91.0)
rubocop (1.90.0)
json (>= 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
@@ -740,7 +710,7 @@ GEM
lint_roller (~> 1.1)
rubocop (>= 1.89.0, < 2.0)
rubocop-ast (>= 1.47.1, < 2.0)
rubocop-rails (2.38.0)
rubocop-rails (2.37.0)
activesupport (>= 4.2.0)
lint_roller (~> 1.1)
rack (>= 1.1)
@@ -757,6 +727,9 @@ GEM
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-rspec (~> 3.5)
ruby-keycloak-admin (26.5.2)
hashie (>= 5.0.0)
httparty (>= 0.21, < 0.25)
ruby-lsp (0.26.11)
language_server-protocol (~> 3.17.0)
prism (>= 1.2, < 2.0)
@@ -769,34 +742,27 @@ GEM
ruby-saml (1.18.1)
nokogiri (>= 1.13.10)
rexml
rubyntlm (0.6.8)
rubyntlm (0.6.5)
base64
rubyzip (3.7.0)
rubyzip (3.5.0)
safety_net_attestation (0.5.0)
jwt (>= 2.0, < 4.0)
samovar (2.5.1)
console (~> 1.0)
sass-embedded (1.105.1)
google-protobuf (~> 4.31)
rake (~> 13.3)
sass-embedded (1.105.1-aarch64-linux-gnu)
google-protobuf (~> 4.31)
sass-embedded (1.105.1-arm64-darwin)
google-protobuf (~> 4.31)
sass-embedded (1.105.1-x86_64-darwin)
google-protobuf (~> 4.31)
sass-embedded (1.105.1-x86_64-linux-gnu)
sass-embedded (1.103.1)
google-protobuf (~> 4.31)
rake (>= 13)
securerandom (0.4.1)
selenium-webdriver (4.50.0)
selenium-webdriver (4.48.0)
base64 (~> 0.2)
logger (~> 1.4)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 4.0)
websocket (~> 1.0)
shoulda-matchers (8.0.1)
activesupport (>= 7.2)
simple_po_parser (1.1.6)
simpleidn (0.3.0)
simpleidn (0.2.3)
slack-ruby-client (3.2.0)
faraday (>= 2.0.1)
faraday-mashify
@@ -841,7 +807,7 @@ GEM
openssl (> 2.0)
openssl-signature_algorithm (~> 1.0)
tsort (0.2.0)
twilio-ruby (7.11.3)
twilio-ruby (7.11.1)
faraday (>= 2.0, < 3.0)
jwt (>= 1.5, < 4.0)
nokogiri (>= 1.6, < 2.0)
@@ -852,11 +818,11 @@ GEM
tzinfo
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
tzinfo-data (1.2026.5)
tzinfo-data (1.2026.3)
tzinfo (>= 1.0.0)
unicode-display_width (3.3.0)
unicode-emoji (~> 4.3)
unicode-emoji (4.3.0)
unicode-display_width (3.2.0)
unicode-emoji (~> 4.1)
unicode-emoji (4.2.0)
uri (1.1.1)
useragent (0.16.11)
validate_url (1.0.15)
@@ -874,7 +840,7 @@ GEM
vite_rails (3.11.1)
railties (>= 5.1, < 9)
vite_ruby (~> 3.0, >= 3.2.2)
vite_ruby (3.11.1)
vite_ruby (3.10.5)
dry-cli (>= 0.7, < 2)
logger (~> 1.6)
mutex_m
@@ -901,11 +867,11 @@ GEM
base64
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
whatsapp_sdk (1.2.0)
whatsapp_sdk (1.1.0)
faraday (~> 2.0, > 2.0.1)
faraday-multipart (~> 1)
zeitwerk (~> 2)
write_xlsx (1.15.1)
write_xlsx (1.15.0)
nkf
rubyzip (>= 2.4.0, < 4.0)
xpath (3.2.0)
@@ -922,11 +888,7 @@ GEM
multipart-post (~> 2.0)
PLATFORMS
aarch64-linux-gnu
arm64-darwin
ruby
x86_64-darwin
x86_64-linux-gnu
DEPENDENCIES
PoParser
@@ -938,7 +900,6 @@ DEPENDENCIES
autodiscover!
autoprefixer-rails
aws-sdk-s3
benchmark
biz
bootsnap
brakeman
@@ -1004,7 +965,6 @@ DEPENDENCIES
overcommit
parallel
pg (~> 1.5, >= 1.5.4)
playwright-ruby-client
pry-doc
pry-rails
pry-remote
@@ -1015,14 +975,13 @@ DEPENDENCIES
pundit
pundit-matchers
rack-attack
rails (~> 8.1.0)
rails (~> 8.0.0)
rake
rchardet (>= 1.8.0)
redis (< 6)
rotp
rspec-rails
rspec-retry
rspec_junit_formatter
rszr
rubocop
rubocop-capybara
@@ -1035,6 +994,7 @@ DEPENDENCIES
rubocop-rake
rubocop-rspec
rubocop-rspec_rails
ruby-keycloak-admin
ruby-lsp
ruby-lsp-rails
ruby-lsp-rspec

View File

@@ -133,12 +133,6 @@ Copyright: Faruk Ates (https://twitter.com/KuraFire)
Richard Herrera (https://twitter.com/doctyper)
License: MIT license & BSD license
-----------------------------------------------------------------------------
playwright_visible_text.js
Source: https://github.com/SeleniumHQ/selenium
Copyright: 2011-2026 Software Freedom Conservancy
2004-2011 Selenium committers
License: Apache License, Version 2.0
-----------------------------------------------------------------------------
qrcodegen.js
Source: https://www.nayuki.io/page/qr-code-generator-library
Copyright: 2022, Project Nayuki

View File

@@ -1 +1 @@
7.3.x
7.2.1

View File

@@ -39,10 +39,10 @@ class App.ControllerAIFeatureBase extends App.ControllerSubContent
# Injects a provider modal into .page-header-meta for the given feature identifier;
# selecting the default entry deletes the feature's routing row. Shown only to provider
# administrators — the backing APIs require admin.ai_provider. The AI provider switch does not
# matter here: routing is configuration and may be prepared while the switch is off.
# administrators — the backing APIs require admin.ai_provider.
renderProviderModal: (identifier) =>
return unless identifier
return unless App.Config.get('ai_provider')
return unless @permissionCheck('admin.ai_provider')
@el.find('.js-featureProviderButton').remove()

View File

@@ -41,15 +41,27 @@ class App.ControllerAIFeatureProviderModal extends App.ControllerModal
@controller?.releaseController()
@controller = new App.ControllerForm(
model: { configure_attributes: @buildProviderAttributes() }
params: { provider_connection_id: App.AIFeatureProvider.valueFor(@row) }
params: @row
autofocus: true
)
@controller.form
buildProviderAttributes: =>
defaultChatProvider = _.find(@providers, (provider) -> provider.default_chat)
options = _.map(@providers, (c) ->
value: c.id
name: c.name
)
options.unshift(
value: ''
name: App.i18n.translateInline('Default (%s)', defaultChatProvider?.name or App.i18n.translateInline('none'))
)
[
{ name: 'provider_connection_id', display: __('Provider'), tag: 'select', null: false, options: App.AIFeatureProvider.options(@providers) }
{ name: 'provider_connection_id', display: __('Provider'), tag: 'select', null: false, options: options }
]
onSubmit: (e) =>
@@ -59,20 +71,49 @@ class App.ControllerAIFeatureProviderModal extends App.ControllerModal
@formDisable(e)
params = @formParam(e.target)
App.AIFeatureProvider.save(
identifier: @featureIdentifier
row: @row
value: params.provider_connection_id
success: =>
@notify(
type: 'success'
msg: App.i18n.translateInline('Provider updated successfully.')
)
@close()
error: (msg) =>
@formEnable(e)
@notify(
type: 'error'
msg: msg
)
)
connectionId = parseInt(params.provider_connection_id, 10) or null
@formEnable(e)
done = =>
@notify(
type: 'success'
msg: App.i18n.translateInline('Provider updated successfully.')
)
@close()
fail = (data) =>
details = data?.responseJSON or {}
@notify(
type: 'error'
msg: details.error_human or details.error or __('The provider could not be updated.')
)
if not connectionId
return @close() if not @row?.id
# Back to the default connection = remove the routing row.
@ajax(
id: 'remove_feature_provider'
type: 'DELETE'
url: App.Config.get('api_path') + '/ai/feature_providers/' + @row.id
success: done
error: fail
)
else if @row?.id
@ajax(
id: 'update_feature_provider'
type: 'PUT'
url: App.Config.get('api_path') + '/ai/feature_providers/' + @row.id
data: JSON.stringify(provider_connection_id: connectionId)
success: done
error: fail
)
else
@ajax(
id: 'create_feature_provider'
type: 'POST'
url: App.Config.get('api_path') + '/ai/feature_providers'
data: JSON.stringify(identifier: @featureIdentifier, provider_connection_id: connectionId)
success: done
error: fail
)

View File

@@ -27,10 +27,38 @@ class FeedbackAndLogs extends App.ControllerAIFeatureBase
# Feedback & Logs works independently of any configured provider connection.
showAlert: -> false
downloadFeedback: ->
App.AIAnalyticsDownload.request(type: 'with_usages', button: @$('.js-downloadFeedback'))
sendDownloadRequest: (type) ->
isFeedback = type is 'with_usages'
downloadErrorLogs: ->
App.AIAnalyticsDownload.request(type: 'errors', button: @$('.js-downloadErrorLogs'))
button = @$(if isFeedback then '.js-downloadFeedback' else '.js-downloadErrorLogs')
disableButton = (disabled) -> button.prop('disabled', disabled)
disableButton(true)
fallbackFilename = if isFeedback then 'ai_analytics_with_usages.xlsx' else 'ai_analytics_errors.xlsx'
App.Ajax.request(
id: 'ai-analytics-download'
type: 'GET'
url: "#{@apiPath}/ai/analytics/download/#{type}"
processData: true
dataType: 'binary'
contentType: 'application/octet-stream'
xhrFields:
responseType: 'blob'
success: (data, status, xhr) ->
App.Utils.downloadFileFromBlob(data, xhr, { fallbackFilename: fallbackFilename })
disableButton(false)
error: (xhr, status, error) =>
@log 'error', error || status
@notify(
type: 'error'
msg: __('The download could not be started. Please try again later.')
)
disableButton(false)
)
downloadFeedback: -> @sendDownloadRequest('with_usages')
downloadErrorLogs: -> @sendDownloadRequest('errors')
App.Config.set('FeedbackAndLogs', { prio: 1060, name: __('Feedback & Logs'), parent: '#ai', target: '#ai/feedback_logs', controller: FeedbackAndLogs, permission: ['admin.ai_feedback_logs'] }, 'NavBarAdmin')

View File

@@ -67,18 +67,34 @@ class TextTool extends App.ControllerAIFeatureBase
@controllerBind('config_update', @configHasChanged)
downloadFeedbackReport: (id) ->
downloadFeedbackReport: (id) =>
text_tool = App.AITextTool.find(id)
filters =
triggered_by_type: 'AI::TextTool'
triggered_by_id: id
url = "#{@apiPath}/ai/analytics/download/with_usages?filters[triggered_by_type]=AI::TextTool&filters[triggered_by_id]=#{id}"
# Only limit the report to feedback since the last reset if one happened;
# otherwise the report should collect everything.
filters.created_after = text_tool.analytics_stats_reset_at if text_tool.analytics_stats_reset_at
if text_tool.analytics_stats_reset_at
url += "&filters[created_after]=#{text_tool.analytics_stats_reset_at}"
App.AIAnalyticsDownload.request(type: 'with_usages', filters: filters)
@ajax(
id: 'download_feedback_report'
type: 'GET'
url: url
processData: true
dataType: 'binary'
contentType: 'application/octet-stream'
xhrFields:
responseType: 'blob'
success: (data, status, xhr) ->
App.Utils.downloadFileFromBlob(data, xhr, { fallbackFilename: 'ai_analytics_with_usages.xlsx' })
error: (xhr, status, error) =>
@log 'error', error || status
@notify(
type: 'error'
msg: __('The download could not be started. Please try again later.')
)
)
resetFeedbackTimestamp: (id) =>
@ajax(

View File

@@ -35,7 +35,6 @@ class App.ControllerTabs extends App.Controller
@html App.view('generic/tabs')(
header: @header
description: @description
subHeader: @subHeader
tabs: displayTabs
addTab: @addTab

View File

@@ -1,368 +0,0 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
class ContentTranslation extends App.ControllerTabs
@requiredPermission: 'admin.integration'
header: __('Translation services')
description: __('This service allows agents to translate articles to their configured UI language or other languages of their choice.')
constructor: ->
super
@title __('Translation services'), true
@tabs = [
{ name: __('Provider Settings'), target: 'provider-settings', controller: ProviderSettings }
{ name: __('Ticket Articles'), target: 'ticket-articles', controller: TicketArticles }
{ name: __('Logs'), target: 'logs', controller: Logs }
{ name: __('Feedback'), target: 'feedback', controller: Feedback }
]
# The tabs read settings while rendering, and App.ControllerTabs#render never releases the
# tab controllers, so the page is rendered once after the settings are loaded.
App.Setting.fetchFull((=> @render()), force: false)
class ProviderSettings extends App.Controller
events:
'submit form': 'submit'
'change [name=provider]': 'changeProvider'
constructor: ->
super
@config = App.Setting.get('content_translation_service_config') || {}
# The connection may be chosen while the AI provider is switched off: routing is configuration,
# the switch only decides whether it is used.
if @permissionCheck('admin.ai_provider')
@fetchAIFeatureProviders()
else
@render()
# The connections to choose from.
fetchAIFeatureProviders: =>
App.AIProviderConnection.fetchFull(
=>
@ajax(
id: 'content_translation_ai_feature_providers'
type: 'GET'
url: "#{App.Config.get('api_path')}/ai/feature_providers"
success: (rows) =>
@aiFeatureProviders = rows
@render()
error: =>
@notify(
type: 'error'
msg: __('The providers could not be loaded.')
)
@render()
)
force: false
)
render: =>
provider = @availableProvider()
attributes = @configurationAttributes(provider)
@html App.view('integration/content_translation_provider_settings')(attributes: attributes)
# The AI service stays selectable while the AI provider is switched off, so that its
# configuration survives the switch. The alert says why nothing translates in the meantime.
if @service('ai').available() and provider is 'ai' and not App.Config.get('ai_provider')
@el.prepend(App.view('ai/missing_provider_alert')(visible: true))
defaults = { ai_provider_connection_id: App.AIFeatureProvider.valueFor(@aiFeatureProvider(provider)) }
params = _.extend(defaults, @config, provider: provider)
@forms = [
new App.ControllerForm(
el: @$('.js-serviceForm')
model: { configure_attributes: [@serviceAttribute()] }
params: params
autofocus: false
)
]
for attribute in attributes
@forms.push(new App.ControllerForm(
el: @$("[data-name='#{attribute.name}']")
model: { configure_attributes: [_.extend({}, attribute, class: 'form-control--small', label_class: 'sr-only')] }
params: params
autofocus: false
))
# Only the service and the AI connection survive the switch: credentials belong to the service
# they were entered for, so a field both services ask for, like the API key, would stay filled.
changeProvider: =>
params = _.extend({}, @config, @formParam(@$('form')))
@config = _.pick(params, 'provider', 'ai_provider_connection_id')
@render()
service: (key) ->
App.Config.get('ContentTranslationServices')[key]
# Only a service that is still offered is kept as the selection: a service that is gone would
# otherwise end up in the select as its raw value.
availableProvider: ->
provider = @config.provider
return if !provider
return if !@service(provider)?.available()
provider
serviceOptions: ->
services = _.filter(_.values(App.Config.get('ContentTranslationServices')), (service) -> service.available())
_.map(_.sortBy(services, (service) -> service.prio), (service) -> { name: service.label, value: service.key })
# customsort keeps the services in the prio order the options were built in.
serviceAttribute: ->
{ name: 'provider', display: __('Translation service provider'), tag: 'select', options: @serviceOptions(), null: true, nulloption: true, translate: true, customsort: 'on' }
configurationAttributes: (provider) ->
(@service(provider)?.credential_attributes or []).concat(@aiProviderAttributes(provider))
# The AI feature the given service runs as, declared on the service entry.
aiFeatureIdentifier: (provider) ->
@service(provider)?.ai_feature_identifier
# The routing row serving that feature, while the admin has pointed it at a connection.
aiFeatureProvider: (provider) ->
identifier = @aiFeatureIdentifier(provider)
return if !identifier
_.find(@aiFeatureProviders, (row) -> row.identifier is identifier)
aiProviderAttributes: (provider) ->
return [] if !@aiFeatureProviders
return [] if !@aiFeatureIdentifier(provider)
[
{ name: 'ai_provider_connection_id', display: __('Provider'), tag: 'select', options: @aiProviderOptions() }
]
aiProviderOptions: ->
App.AIFeatureProvider.options(App.AIProviderConnection.all())
submit: (e) =>
e.preventDefault()
params = @formParam(e.target)
errors = {}
for form in @forms
_.extend(errors, form.validate(params))
if !_.isEmpty(errors)
@formValidate(form: e.target, errors: errors)
return
# The routing row is written first: the settings announce the success of the whole form, so
# they must not run while the row this form also changed is still the old one.
@submitAIFeatureProvider(params.provider, params.ai_provider_connection_id, => @submitConfig(params))
# The flag follows the config only once the config is saved, so a refused config leaves the flag
# alone and the admin sees one error instead of an error next to a success.
submitConfig: (params) ->
# Without a service there is nothing to keep, not even the credentials of the previous one.
config = if params.provider then _.omit(params, 'ai_provider_connection_id') else {}
hasProvider = !!params.provider
form = @$('form')
App.ControllerForm.disable(form)
App.Setting.set('content_translation_service_config', config,
notify: false
failLocal: -> App.ControllerForm.enable(form)
doneLocal: =>
App.ControllerForm.enable(form)
if App.Config.get('content_translation_service') is hasProvider
@notify(type: 'success', msg: __('Update successful.'), timeout: 2000)
return
App.Setting.set('content_translation_service', hasProvider, notify: true)
)
# Calls back only once the row is written, so a failure stops the submit instead of ending in a
# success and an error notification next to each other.
submitAIFeatureProvider: (provider, value, done) =>
return done() if !value
identifier = @aiFeatureIdentifier(provider)
App.AIFeatureProvider.save(
identifier: identifier
row: @aiFeatureProvider(provider)
value: value
success: (updated) =>
@replaceAIFeatureProvider(identifier, updated)
done()
error: (msg) =>
@notify(type: 'error', msg: msg)
)
# Keeps the rows in step with what was just written, so a second submit updates the row it
# created instead of trying to create it again.
replaceAIFeatureProvider: (identifier, row) =>
@aiFeatureProviders = _.reject(@aiFeatureProviders, (existing) -> existing.identifier is identifier)
@aiFeatureProviders.push(row) if row
class TicketArticles extends App.Controller
events:
'change .js-ticketArticleSetting input': 'toggleTicketArticle'
'change .js-ticketArticleAutoSetting input': 'toggleTicketArticleAuto'
'submit form': 'submit'
elements:
'.js-ticketArticleSetting input': 'ticketArticleSetting'
'.js-ticketArticleAutoSetting input': 'ticketArticleAutoSetting'
constructor: ->
super
@render()
# Follow the switches in place instead of rendering again, so a pushed setting change (also the
# one a switch on this page caused) does not discard an unsaved role selection.
@controllerBind('config_update', (data) =>
switch data.name
when 'content_translation_ticket_article'
@ticketArticleSetting.prop('checked', App.Config.get(data.name))
when 'content_translation_ticket_article_auto'
@ticketArticleAutoSetting.prop('checked', App.Config.get(data.name))
)
# The roles and the language detection are no frontend settings, so they are followed via the
# collection. Only a role change from elsewhere is drawn; the own submit already shows what it saved.
@subscribeId = App.Setting.subscribe(@settingsChanged, initFetch: true, clear: false)
settingsChanged: =>
@renderAlert()
return if _.isEqual(App.Setting.get('content_translation_ticket_article_auto_role_ids'), @renderedRoleIds)
@render()
render: =>
@html App.view('integration/content_translation_ticket_articles')(
ticketArticle: App.Config.get('content_translation_ticket_article')
ticketArticleAuto: App.Config.get('content_translation_ticket_article_auto')
alert: App.i18n.translateContent('The article language detection is disabled. Enable it to avoid translating articles that are already in the target language. %l', '#settings/ticket')
)
@renderAlert()
@renderedRoleIds = App.Setting.get('content_translation_ticket_article_auto_role_ids')
new App.ControllerForm(
el: @$('.js-form')
model:
configure_attributes: [
{ name: 'content_translation_ticket_article_auto_role_ids', display: __('Available for the following roles'), tag: 'column_select', null: true, relation: 'Role', translate: true }
]
params:
content_translation_ticket_article_auto_role_ids: @renderedRoleIds
autofocus: false
)
toggleTicketArticle: =>
App.Setting.set('content_translation_ticket_article', @ticketArticleSetting.prop('checked'), failLocal: @render, notify: true)
toggleTicketArticleAuto: =>
App.Setting.set('content_translation_ticket_article_auto', @ticketArticleAutoSetting.prop('checked'), failLocal: @render, notify: true)
submit: (e) =>
e.preventDefault()
roleIds = @formParam(e.target).content_translation_ticket_article_auto_role_ids || []
# Known as shown before the request, so neither the local change nor the push redraws the form.
@renderedRoleIds = roleIds
App.Setting.set('content_translation_ticket_article_auto_role_ids', roleIds, failLocal: @render, notify: true)
renderAlert: =>
@$('.js-languageDetectionAlert').toggleClass('hide', !!App.Setting.get('language_detection_article'))
release: =>
App.Setting.unsubscribe(@subscribeId)
class Logs extends App.Controller
constructor: ->
super
@render()
@subscribeId = App.Setting.subscribe(@renderAiState, initFetch: true, clear: false)
render: =>
@html App.view('integration/content_translation_logs')(hint: @aiHint())
# The heading belongs to the tab, so it stays while the log itself makes way for the AI hint.
@httpLog = new App.HttpLog(
el: @$('.js-log')
facility: 'content_translation'
header: false
)
# Linked only for whoever may open that screen: the link would otherwise just bounce off
# permissionCheckRedirect.
aiHint: ->
if @permissionCheck('admin.ai_feedback_logs')
return App.i18n.translateContent('You can find relevant AI provider logs under AI > Feedback & Logs. %l', '#ai/feedback_logs')
App.i18n.translateContent('You can find relevant AI provider logs under AI > Feedback & Logs.')
renderAiState: =>
isAI = App.Setting.get('content_translation_service_config')?.provider is 'ai'
@$('.js-aiHint').toggleClass('hide', !isAI)
@$('.js-log').toggleClass('hide', isAI)
release: =>
App.Setting.unsubscribe(@subscribeId)
@httpLog?.releaseController()
@httpLog = null
class Feedback extends App.Controller
# The page asks for admin.integration only, but the download answers to admin.ai_feedback_logs, so
# App.ControllerTabs leaves out the tab for whoever lacks it instead of offering a failing button.
@requiredPermission: 'admin.ai_feedback_logs'
events:
'click .js-downloadFeedback': 'downloadFeedback'
constructor: ->
super
@render()
render: =>
@html App.view('integration/content_translation_feedback')()
# Hardcoded, as Service::ContentTranslation::StoredTranslation::IDENTIFIER does not reach the
# frontend. It covers the AI service and the others alike.
downloadFeedback: =>
App.AIAnalyticsDownload.request(
type: 'with_usages'
filters: { identifier: 'translate' }
button: @$('.js-downloadFeedback')
)
class State
@current: ->
not _.isEmpty(App.Setting.get('content_translation_service_config'))
App.Config.set(
'IntegrationContentTranslation'
{
name: __('Translation services')
target: '#system/integration/content_translation'
description: __('Translate ticket articles into another language.')
controller: ContentTranslation
state: State
permission: ['admin.integration']
}
'NavBarIntegrations'
)

View File

@@ -62,10 +62,13 @@ class Form extends App.Controller
)
# placeholder
configure_attributes = [
{ name: 'user_ids', display: '', tag: 'column_select', multiple: true, null: true, relation: 'User', sortBy: 'displayName' },
]
new App.ControllerForm(
el: @$('.js-userSelectorBlank')
model:
configure_attributes: @userSelectorAttributes()
configure_attributes: configure_attributes,
params:
user_ids: []
autofocus: false
@@ -96,35 +99,18 @@ class Form extends App.Controller
)
for row in @config.notify_map
configure_attributes = [
{ name: 'user_ids', display: '', tag: 'column_select', multiple: true, null: true, relation: 'User', sortBy: 'displayName' },
]
new App.ControllerForm(
el: @$("[name=queue][value='#{row.queue}']").closest('tr').find('.js-userSelector')
model:
configure_attributes: @userSelectorAttributes()
configure_attributes: configure_attributes,
params:
user_ids: row.user_ids
autofocus: false
)
# Only a user with the cti.agent permission ever sees a caller log, so the
# filter offers no one else. The form runs it twice, over the records and
# over the built options, so an entry carries the user as id or as value.
userSelectorAttributes: ->
[
{
name: 'user_ids'
display: ''
tag: 'column_select'
multiple: true
null: true
relation: 'User'
sortBy: 'displayName'
filter: (entries) ->
entries.filter (entry) ->
user = App.User.find(entry.id || entry.value)
user && user.permission('cti.agent')
}
]
updateCurrentConfig: =>
config = @config
cleanupInput = @cleanupInput

View File

@@ -24,16 +24,6 @@ class Maintenance extends App.Controller
@maintenanceForceRefresh(data)
)
# Import mode is switched by the import controllers and background services,
# never via the websocket maintenance event, so react to the setting itself.
@controllerBind('config_update_local', (data) =>
return if data.name isnt 'import_mode'
return if @permissionCheck('admin.maintenance')
# A switched session is judged by the switching user, which only the backend knows.
return if @Config.get('switch_back_to_possible')
@maintenanceMode(on: data.value)
)
showMessage: (message = {}) =>
if message.reload
@disconnectClient()

View File

@@ -27,10 +27,6 @@ class App.UiElement.postmaster_set extends App.UiElement.ApplicationAction
if !row.readonly
config = _.clone(row)
# values may contain placeholders like #{regexp.NAME}, which fail the native field validation
if config.type is 'email' || config.type is 'tel' || config.type is 'url'
config.type = 'text'
switch config.tag
when 'date'
config.operator = ['static', 'relative']

View File

@@ -120,11 +120,6 @@ class App.CustomerChat extends App.Controller
chat_sessions: list
)
# Bootstrap's popover sanitizer has no <time> in its allowlist and would drop
# the relative time rendered by the humanTime helper together with its text.
chatSessionListWhiteList: ->
$.extend({}, $.fn.popover.Constructor.DEFAULTS.whiteList, time: ['datetime', 'title'])
show: (params) =>
@title(__('Customer Chat'), true)
@@ -258,7 +253,6 @@ class App.CustomerChat extends App.Controller
@popovers.push @el.find(".js-waitingCustomers[data-chat-id=#{chat.id}] .js-info").popover(
trigger: 'hover'
html: true
whiteList: @chatSessionListWhiteList()
animation: false
delay: 0
placement: 'bottom'
@@ -273,7 +267,6 @@ class App.CustomerChat extends App.Controller
@popovers.push @el.find('.js-waitingCustomers .js-totalInfo').popover(
trigger: 'hover'
html: true
whiteList: @chatSessionListWhiteList()
animation: false
delay: 0
placement: 'bottom'
@@ -296,7 +289,6 @@ class App.CustomerChat extends App.Controller
@popovers.push @el.find('.js-chattingCustomers .js-info').popover(
trigger: 'hover'
html: true
whiteList: @chatSessionListWhiteList()
animation: false
delay: 0
placement: 'bottom'

View File

@@ -66,8 +66,7 @@ class Login extends App.ControllerFullPage
data.name != 'product_name' &&
data.name != 'product_logo' &&
data.name != 'fqdn' &&
data.name != 'user_show_password_login' &&
data.name != 'import_mode'
data.name != 'user_show_password_login'
@render()
'rerender'
)

View File

@@ -166,7 +166,6 @@ class App.Search extends App.Controller
parent: @
parentEl: elLocal
appEl: @appEl
dragEnabled: => @model is 'Ticket'
batchSuccess: =>
@search(0, true)
)
@@ -362,7 +361,6 @@ class App.Search extends App.Controller
'click': callbackCheckbox
select_all: callbackCheckbox
sortClickCallback: @saveOrderBy
callbackHeader: [ @markRelationHeadersUnsortable ]
pagerAjax: true
)
@@ -425,7 +423,6 @@ class App.Search extends App.Controller
events:
'click': openObject
sortClickCallback: @saveOrderBy
callbackHeader: [ @markRelationHeadersUnsortable ]
pagerEnabled: false
orderEnabled: false
pagerAjax: true
@@ -471,11 +468,9 @@ class App.Search extends App.Controller
newColumn = $(e.currentTarget).closest('[data-column-key]').attr('data-column-key')
config = _.find App[@model].configure_attributes, (elem) -> elem.name == newColumn
return if !config
# Search results are sorted by Elasticsearch, which can only sort a relation by
# its raw reference (e.g. customer_id) and not by the displayed name.
return if config.relation
# There's no reliable way to sort to-many relations. Sorry.
return if config.multiple && config.relation
current = @getSavedOrderBy()
@@ -544,11 +539,6 @@ class App.Search extends App.Controller
getSavedOrderBy: =>
@savedOrderBy[@model]
markRelationHeadersUnsortable: (headers) ->
for header in headers when header.relation
header.unsortable = true
headers
saveOrderBy: (table) =>
return if !table

View File

@@ -248,7 +248,6 @@ class App.TicketOverviewTable extends App.Controller
attribute =
name: 'icon_priority'
display: ''
parentClass: 'table-priority-icon'
translation: false
width: '24px'
displayWidth: 24

View File

@@ -37,14 +37,12 @@ class App.TicketZoomTimeAccountingModal extends App.ControllerModal
{ name: 'time_unit', display: __('Accounted Time'), tag: 'input', type: 'text', null: false, placeholder: __('Enter the time you want to record'), appendText: @timeAccountingDisplayUnit() }
]
activeTypes = App.TicketTimeAccountingType.search(filter: { active: true })
if @Config.get('time_accounting_types')
# An empty type list would only render an empty select, so the field needs an active type
# next to the enabled setting.
if @Config.get('time_accounting_types') and _.isEmpty(activeTypes) is false
# Pre-select the default activity type, but only if it's still active.
defaultTypeId = _.findWhere(activeTypes, { id: @Config.get('time_accounting_type_default') })?.id
# Pre-select the default activity type, but only if it's active.
if @Config.get('time_accounting_type_default')
defaultType = App.TicketTimeAccountingType.find(@Config.get('time_accounting_type_default'))
defaultTypeId = defaultType.active and defaultType.id
configure_attributes.push
name: 'accounted_time_type_id'

View File

@@ -33,7 +33,6 @@ class App.HttpLog extends App.Controller
@html App.view('widget/http_log')(
records: @records
header: @header # false when the embedding screen draws the heading itself
description: @description
relatedObjects: relatedObjects
hasRelatedObjects: !_.isEmpty(relatedObjects)

View File

@@ -75,8 +75,6 @@ class App.TicketBatch extends App.Controller
))
startDragItem: (event) =>
return if @dragEnabled && !@dragEnabled()
event.preventDefault()
App.TicketOverviewCollection.fetch()

View File

@@ -78,7 +78,6 @@ class App.TicketList extends App.Controller
attribute =
name: 'icon_priority'
display: ''
parentClass: 'table-priority-icon'
translation: false
width: '22px'
displayWidth: 22
@@ -112,9 +111,6 @@ class App.TicketList extends App.Controller
callbackHeader = [ callbackIconHeader, callbackIconPriorityHeader ]
callbackAttributes.icon_priority = [ callbackIconPriority ]
if @callbackHeader
callbackHeader = callbackHeader.concat(@callbackHeader)
list = []
for ticket_id in @ticket_ids
ticketItem = App.Ticket.fullLocal(ticket_id)

View File

@@ -1,29 +0,0 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
class App.AIAnalyticsDownload
# @param type [String] the report type, `with_usages` or `errors`
# @param filters [Object] narrows the report down, keyed like the filters of the endpoint
# @param button [jQuery, undefined] disabled while the request runs
@request: ({ type, filters = {}, button }) ->
button?.prop('disabled', true)
App.Ajax.request(
type: 'GET'
url: "#{App.Config.get('api_path')}/ai/analytics/download/#{type}"
data: { filters: filters }
processData: true
dataType: 'binary'
contentType: 'application/octet-stream'
xhrFields:
responseType: 'blob'
success: (data, status, xhr) ->
App.Utils.downloadFileFromBlob(data, xhr, { fallbackFilename: "ai_analytics_#{type}.xlsx" })
button?.prop('disabled', false)
error: (xhr, status, error) ->
App.Log.error('App.AIAnalyticsDownload', error || status)
App.Event.trigger('notify',
type: 'error'
msg: __('The download could not be started. Please try again later.')
)
button?.prop('disabled', false)
)

View File

@@ -1,70 +0,0 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
# Routes an AI feature to a provider connection: the options an admin picks from, and the routing
# row written behind the pick. Shared by the provider modal of the AI feature pages and the
# translation integration.
class App.AIFeatureProvider
# The option that stands for "no routing row" - the default connection serves the feature.
@defaultValue: 'default'
# @param connections [Array<App.AIProviderConnection>]
# @return [Array<Object>] select options, the default entry first
@options: (connections) ->
connections = _.sortBy(connections, 'name')
defaultConnection = _.find(connections, (connection) -> connection.default_chat)
# String values, because the form hands every value back as a string. A numeric value would
# stop matching after a re-render, and the select would invent a "historical" option for it.
options = _.map(connections, (connection) -> { name: connection.name, value: connection.id.toString() })
options.unshift(
name: App.i18n.translateInline('Default (%s)', defaultConnection?.name or App.i18n.translateInline('none'))
value: @defaultValue
)
options
# @param row [Object, undefined] the routing row of the feature, if there is one
# @return [String] the option value that preselects it
@valueFor: (row) ->
row?.provider_connection_id?.toString() or @defaultValue
# Writes the routing row the picked option stands for. `success` receives the row as the server
# returned it, or null once the default entry removed the row; `error` receives a message.
@save: ({ identifier, row, value, success, error }) ->
failed = (xhr) ->
details = xhr?.responseJSON or {}
error(details.error_human or details.error or __('The provider could not be updated.'))
if value is @defaultValue
return success(null) if !row
# Back to the default connection = remove the routing row.
return App.Ajax.request(
id: 'ai_feature_provider_save'
type: 'DELETE'
url: "#{App.Config.get('api_path')}/ai/feature_providers/#{row.id}"
success: -> success(null)
error: failed
)
data = { provider_connection_id: parseInt(value, 10) }
if row
App.Ajax.request(
id: 'ai_feature_provider_save'
type: 'PUT'
url: "#{App.Config.get('api_path')}/ai/feature_providers/#{row.id}"
data: JSON.stringify(data)
success: success
error: failed
)
else
App.Ajax.request(
id: 'ai_feature_provider_save'
type: 'POST'
url: "#{App.Config.get('api_path')}/ai/feature_providers"
data: JSON.stringify(_.extend({ identifier }, data))
success: success
error: failed
)

View File

@@ -1,19 +0,0 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
# Keep in sync with the backends in app/services/service/content_translation/backend/.
App.Config.set('ai', {
key: 'ai'
label: __('AI provider')
prio: 1000
# ControllerForm attributes for the credentials of this service. AI keeps its credentials in
# the AI provider manager, so it has nothing of its own to ask for.
credential_attributes: []
ai_feature_identifier: 'translate'
# An installation without AI at all deactivates its permission - then the service is not offered
# either. A switched-off AI provider is a different case: the switch can come back, so the
# service stays selectable and the integration warns about the switch instead.
available: -> App.Permission.findByAttribute('name', 'admin.ai_provider')?.active isnt false
}, 'ContentTranslationServices')

View File

@@ -1,26 +0,0 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
# Keep in sync with the backends in app/services/service/content_translation/backend/.
App.Config.set('deepl', {
key: 'deepl'
label: __('DeepL')
prio: 3000
credential_attributes: [
{
name: 'tier'
display: __('API plan')
tag: 'select'
null: false
nulloption: true
translate: true
options: [
{ name: __('Developer API (api-free.deepl.com)'), value: 'free' }
{ name: __('Growth/Enterprise API (api.deepl.com)'), value: 'pro' }
]
}
{ name: 'api_key', display: __('API key'), tag: 'input', type: 'text', input_type: 'password', null: false }
]
available: -> true
}, 'ContentTranslationServices')

View File

@@ -1,19 +0,0 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
# Keep in sync with the backends in app/services/service/content_translation/backend/.
App.Config.set('libre_translate', {
key: 'libre_translate'
label: __('LibreTranslate')
prio: 2000
# ControllerForm attributes for the credentials of this service. The names are the keys the
# backend reads from the service config, and 'api_key' is also the spelling the settings
# controller masks on.
credential_attributes: [
{ name: 'url', display: __('URL'), tag: 'input', type: 'url', null: false, placeholder: 'https://libretranslate.com' }
{ name: 'api_key', display: __('API key'), tag: 'input', type: 'text', input_type: 'password', null: true }
]
# Unlike AI, LibreTranslate has no part of the installation it could be missing with.
available: -> true
}, 'ContentTranslationServices')

View File

@@ -27,10 +27,10 @@ class App.PopoverProvider
build: (buildParams) ->
return if !@checkPermissions()
@clear()
@clear(@popovers)
@bind() if !buildParams.doNotBind
return if buildParams.isTouchDevice is true
@buildPopovers()
@popovers = @buildPopovers()
checkPermissions: ->
@params.parentController.permissionCheck(@constructor.permission)
@@ -49,7 +49,7 @@ class App.PopoverProvider
selector = supplementaryData.selector || ".#{@cssClass()}"
elements = @params.parentController.el.find(selector).popover('destroy').popover(
@params.parentController.el.find(selector).popover('destroy').popover(
trigger: 'hover'
container: 'body'
html: true
@@ -62,21 +62,16 @@ class App.PopoverProvider
content: ->
context.buildContentFor(@, supplementaryData)
)
elements.on('show.bs.popover', (e) -> context.onShow(e, @))
elements.on('hide.bs.popover', (e) -> context.onHide(e, @))
# A re-render strips the old trigger elements' data, so `popover('destroy')` on them is a no-op
# and leaves an already shown tip behind in the body.
@popoverInstances = elements.map(-> $(@).data('bs.popover')).get()
@params.parentController.el.find(selector).on('show.bs.popover', (e) -> context.onShow(e, @))
@params.parentController.el.find(selector).on('hide.bs.popover', (e) -> context.onHide(e, @))
clear: ->
return if !@popoverInstances
popover.destroy() for popover in @popoverInstances when popover.$element
@popoverInstances = null
return if !@popovers
@popovers.popover('destroy')
hide: ->
return if !@popoverInstances
popover.hide() for popover in @popoverInstances
return if !@popovers
@popovers.popover('hide')
buildTitleFor: (elem) ->
'title'

View File

@@ -19,9 +19,8 @@ class UserTicket extends App.PopoverProviderAjax
build: (buildParams) ->
return if !@checkPermissions()
@clear()
@buildParams = buildParams
@buildPopovers(ticketList: {}, selector: buildParams.selector)
@popovers = @buildPopovers(ticketList: {}, selector: buildParams.selector)
buildContentFor: (elem, supplementaryData) ->
return super if _.isEmpty(supplementaryData.ticketList)

View File

@@ -109,14 +109,13 @@ class App.SearchableAjaxSelect extends App.SearchableSelect
renderResponseItemAjax: (elem, data) ->
result = _.find(data.details, (detailElem) -> detailElem.type == elem.type and detailElem.id == elem.id)
# The details are escaped HTML, while the option template escapes what it is given.
category = undefined
if result.type is 'KnowledgeBase::Answer::Translation' && result.subtitle
category = App.Utils.html2text(result.subtitle)
category = result.subtitle
if result
{
category: category
name: App.Utils.html2text(result.title)
name: result.title
value: elem.id
}

View File

@@ -686,10 +686,9 @@
.result
.map(function(elem) {
if(result = _.find(data.details, function(detailElem) { return detailElem.type == elem.type && detailElem.id == elem.id })) {
// The details are escaped HTML, while the list below shows them as text.
return {
'category': App.Utils.html2text(result.subtitle),
'name': App.Utils.html2text(result.title),
'category': result.subtitle,
'name': result.title,
'value': elem.id,
'url': result.url
}

View File

@@ -1,2 +1,4 @@
<%# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/ %>
<p><%- @T('Semantic search will stop working until another provider is selected for it.') %></p>
<p><%- @T('The knowledge base index is kept, so selecting the same provider again requires no re-index.') %></p>

View File

@@ -1,3 +1,3 @@
<div class="js-missingProviderAlert alert alert--warning <% if !@visible: %>hide<% end %>">
<%- @T('The AI provider configuration is disabled. Before proceeding, please set up at least one provider in |AI > Providers|. %l', '#ai/providers') %>
<%- @T('The provider configuration is disabled. Before proceeding, please set up at least one provider in |AI > Providers|.') %>
</div>

View File

@@ -1,3 +1,4 @@
<%# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/ %>
<div class="zammad-switch zammad-switch--small js-ai-provider-toggle">
<input name="ai_provider" type="checkbox" id="setting-ai_provider" aria-label="<%- @T('Enable AI provider') %>" <% if @ai_provider: %>checked<% end %>>
<label for="setting-ai_provider" aria-hidden="true"></label>

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