Compare commits
310 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b753d4fb0 | ||
|
|
ee95993569 | ||
|
|
e732fa5107 | ||
|
|
ee04322a63 | ||
|
|
d66f7c9d68 | ||
|
|
9fd8a6ad87 | ||
|
|
416fe41626 | ||
|
|
9f8f2388e7 | ||
|
|
911cb27aa6 | ||
|
|
24277638f9 | ||
|
|
110fa3339c | ||
|
|
1e486439ef | ||
|
|
9a1439e7fc | ||
|
|
713173c2a7 | ||
|
|
0b3f314645 | ||
|
|
551d4f7fac | ||
|
|
ed181aaa76 | ||
|
|
65b7d0843b | ||
|
|
50bdaae0ac | ||
|
|
0228d7dbd6 | ||
|
|
465b2a8f43 | ||
|
|
1129cc4f03 | ||
|
|
060ea46b1b | ||
|
|
a74ed5fbba | ||
|
|
98e279a474 | ||
|
|
ebe4b7a454 | ||
|
|
31794c4b56 | ||
|
|
7d7975eee5 | ||
|
|
045a91746b | ||
|
|
a26b9ad2de | ||
|
|
5e1c80bcb0 | ||
|
|
c0c5aa3d38 | ||
|
|
6f5ca10dfe | ||
|
|
ea9dc41f50 | ||
|
|
54e9f987eb | ||
|
|
151f641c1d | ||
|
|
b77d668f5d | ||
|
|
2e7daf6b19 | ||
|
|
1200360977 | ||
|
|
97a2c5e030 | ||
|
|
70587e634d | ||
|
|
48efde6c79 | ||
|
|
c45ccd7e7e | ||
|
|
db1d5ec219 | ||
|
|
bc067ef3f0 | ||
|
|
5fb14cbae6 | ||
|
|
2731289bf3 | ||
|
|
579146276f | ||
|
|
e4cebad6ca | ||
|
|
cec59d3b6b | ||
|
|
f51bedcf5e | ||
|
|
075a5cc32a | ||
|
|
38c00436e2 | ||
|
|
9f29391db4 | ||
|
|
677aef4b1d | ||
|
|
945b7369ce | ||
|
|
c3a566e783 | ||
|
|
f7048b1308 | ||
|
|
48312937df | ||
|
|
5dfbb30150 | ||
|
|
0577200f87 | ||
|
|
6e1d0882ef | ||
|
|
6edde8a670 | ||
|
|
98da351d09 | ||
|
|
bb69454ca8 | ||
|
|
8cb978bda1 | ||
|
|
e0aad7951c | ||
|
|
9e5237a033 | ||
|
|
3d2815aecc | ||
|
|
8935f1b4c3 | ||
|
|
2e696f91aa | ||
|
|
2058e0ee3f | ||
|
|
023601fc5e | ||
|
|
3ddf995ed2 | ||
|
|
600ab13125 | ||
|
|
b665173bc6 | ||
|
|
da9cf5813a | ||
|
|
e5fbbba94e | ||
|
|
7b834fab61 | ||
|
|
362a54ab39 | ||
|
|
5970fba843 | ||
|
|
5a5c7650a7 | ||
|
|
f71cd98c99 | ||
|
|
d274a3273e | ||
|
|
11b1461156 | ||
|
|
4234be8201 | ||
|
|
409eff8afa | ||
|
|
6437f46e07 | ||
|
|
f6b741d5fb | ||
|
|
15fc26ea81 | ||
|
|
480ca9d4fe | ||
|
|
db8590f692 | ||
|
|
17e0e558eb | ||
|
|
0e13236e75 | ||
|
|
13c599ad2d | ||
|
|
7d0d97a065 | ||
|
|
434370a1f2 | ||
|
|
9a6ec511ed | ||
|
|
c6d7c338f9 | ||
|
|
d57b643e08 | ||
|
|
1e17bef2ab | ||
|
|
db5e805ee0 | ||
|
|
528910f1f6 | ||
|
|
1b8b3894c4 | ||
|
|
0f47ae5359 | ||
|
|
b1dfbc3eb1 | ||
|
|
07889decc9 | ||
|
|
8a42e2f2ed | ||
|
|
d203f43a43 | ||
|
|
ff5b8af113 | ||
|
|
6a4baf7c85 | ||
|
|
62163034dd | ||
|
|
cf60dd7359 | ||
|
|
bcbc29436a | ||
|
|
c63f3625ea | ||
|
|
18e00d947b | ||
|
|
914b9f596b | ||
|
|
caae658587 | ||
|
|
33e72ba0f4 | ||
|
|
619ef87bb2 | ||
|
|
68277d9888 | ||
|
|
6734a65ac9 | ||
|
|
3ae1fa15e5 | ||
|
|
9eb6fa00f9 | ||
|
|
55b0d56dbe | ||
|
|
952d9a4a58 | ||
|
|
1825040ffe | ||
|
|
ac9fc5aaac | ||
|
|
27072339e0 | ||
|
|
991f9ac871 | ||
|
|
24eed6dd3f | ||
|
|
bdc203b567 | ||
|
|
c2e1e8f389 | ||
|
|
63940b512f | ||
|
|
e20dd5f95d | ||
|
|
5db43b3af1 | ||
|
|
cd1640e591 | ||
|
|
6775944657 | ||
|
|
42ba46bc8a | ||
|
|
d2ac472902 | ||
|
|
71c22ef656 | ||
|
|
aafa1d635e | ||
|
|
950c5b61b6 | ||
|
|
521e7a6fdb | ||
|
|
a924a39905 | ||
|
|
08582fea8a | ||
|
|
9cc5fabd53 | ||
|
|
d675cde614 | ||
|
|
5b396c16c1 | ||
|
|
aa6e2d44ec | ||
|
|
d4a704eb84 | ||
|
|
0c5234a571 | ||
|
|
b9b1b27f8f | ||
|
|
9d92a60807 | ||
|
|
6af6c459a0 | ||
|
|
3a841fbd4c | ||
|
|
447e9c0d95 | ||
|
|
f5aed5ec81 | ||
|
|
1ad0d928a4 | ||
|
|
50afc49814 | ||
|
|
df5b7e16b6 | ||
|
|
a766089e94 | ||
|
|
ab9b64d571 | ||
|
|
a7385ec42e | ||
|
|
8226a0a2e2 | ||
|
|
4166a43ae0 | ||
|
|
6f6ea1a880 | ||
|
|
35766eadde | ||
|
|
662245aca9 | ||
|
|
9e84eb1b59 | ||
|
|
ca0015a9fb | ||
|
|
54ee63f620 | ||
|
|
4be72d4ca8 | ||
|
|
cb0c223f43 | ||
|
|
6d3d2456ea | ||
|
|
55da798cbd | ||
|
|
0afee62ecf | ||
|
|
c76fc69ad2 | ||
|
|
7ece397217 | ||
|
|
fc761060c3 | ||
|
|
22f7ec2b18 | ||
|
|
3e42f8d238 | ||
|
|
1ae9dc3bd5 | ||
|
|
3c7d16f786 | ||
|
|
451268cfcd | ||
|
|
3a18bb052c | ||
|
|
5018dd3ad2 | ||
|
|
b57d4cb861 | ||
|
|
4916ff4ede | ||
|
|
0a9e45fc62 | ||
|
|
af8e28daea | ||
|
|
494ba95e20 | ||
|
|
e7a30a210a | ||
|
|
f399d51b27 | ||
|
|
0ba441c65d | ||
|
|
495daf94f6 | ||
|
|
e9068b270e | ||
|
|
c545ff10b2 | ||
|
|
301f2b11e1 | ||
|
|
281e8105c3 | ||
|
|
ab8bba1970 | ||
|
|
9e3da6f931 | ||
|
|
c3268f811d | ||
|
|
847138afaa | ||
|
|
8796ac43d5 | ||
|
|
af5b5f702f | ||
|
|
b4ec46ec8e | ||
|
|
6598ed3690 | ||
|
|
c18867f7c8 | ||
|
|
f554c40ada | ||
|
|
8ccec554fa | ||
|
|
0a6b5454eb | ||
|
|
cb35c689d1 | ||
|
|
6324aacef6 | ||
|
|
11ec201fdb | ||
|
|
ec915a3991 | ||
|
|
eab302d2ee | ||
|
|
556e1c8420 | ||
|
|
08df09e7c3 | ||
|
|
49186ec781 | ||
|
|
ed740442a2 | ||
|
|
76a2bec1f0 | ||
|
|
0291a23eef | ||
|
|
3e7d711b5f | ||
|
|
01be3bf121 | ||
|
|
e1c8c1c4be | ||
|
|
925bea2505 | ||
|
|
7ac0d216e7 | ||
|
|
858e6fcf04 | ||
|
|
ba8af9e282 | ||
|
|
a2057dcf81 | ||
|
|
e8902e14f1 | ||
|
|
e5072ccd46 | ||
|
|
6a287c5e1a | ||
|
|
b482ba7463 | ||
|
|
7e950b7292 | ||
|
|
02f3349010 | ||
|
|
f44c67a54e | ||
|
|
203b01bd53 | ||
|
|
8f6e723119 | ||
|
|
f2d2f3c820 | ||
|
|
08f75cf405 | ||
|
|
66b3af0705 | ||
|
|
86bdef31b2 | ||
|
|
5124de7d3e | ||
|
|
71678141d5 | ||
|
|
646d6bafc1 | ||
|
|
a743fd43b6 | ||
|
|
965435f237 | ||
|
|
67e58fcc64 | ||
|
|
fd2bdd75d2 | ||
|
|
218df29906 | ||
|
|
d9c5ba05f3 | ||
|
|
1c4bbbcb48 | ||
|
|
b3b3b78626 | ||
|
|
37bac7726f | ||
|
|
5003c8d311 | ||
|
|
147ab7b060 | ||
|
|
bbd992bc55 | ||
|
|
dee9b72eb9 | ||
|
|
01f5ad5717 | ||
|
|
90e59a98fd | ||
|
|
7736d0365f | ||
|
|
2a34cba995 | ||
|
|
9e069ada35 | ||
|
|
6f382b08ec | ||
|
|
0d86c485bf | ||
|
|
f6114a6aca | ||
|
|
ac93615eba | ||
|
|
9b53dd616a | ||
|
|
a1a82283f8 | ||
|
|
303cceece9 | ||
|
|
70982e5163 | ||
|
|
c2c8fb4c80 | ||
|
|
84e4b27e8c | ||
|
|
2ff8ac47e6 | ||
|
|
2655bb0f44 | ||
|
|
8a8df58623 | ||
|
|
0d53457279 | ||
|
|
daa3ac83e6 | ||
|
|
7e03016fe7 | ||
|
|
1b3b73e0fb | ||
|
|
8f71883121 | ||
|
|
b605a7f82c | ||
|
|
64e138c8ec | ||
|
|
1192a9c49a | ||
|
|
0c22c51356 | ||
|
|
669958389c | ||
|
|
3959bba0f0 | ||
|
|
8106ada2db | ||
|
|
3551a84b8a | ||
|
|
ad76b10694 | ||
|
|
5659c1a686 | ||
|
|
c21e0c1b3e | ||
|
|
4c421f0b2c | ||
|
|
ffa06c0516 | ||
|
|
b69c301dec | ||
|
|
0eaf215e15 | ||
|
|
7e0c22f72e | ||
|
|
533acef6bd | ||
|
|
3e31091d7d | ||
|
|
35667d2599 | ||
|
|
d671bf22c1 | ||
|
|
4e489121c5 | ||
|
|
db07311818 | ||
|
|
031a514395 | ||
|
|
96ca490c7f | ||
|
|
2f45c7b2f1 | ||
|
|
01124d9023 | ||
|
|
8777c6c185 |
@@ -1,2 +1,2 @@
|
||||
https://github.com/heroku/heroku-buildpack-nodejs.git#v365
|
||||
https://github.com/heroku/heroku-buildpack-nodejs.git#v371
|
||||
https://github.com/pkgr/heroku-buildpack-ruby.git#v361-1
|
||||
@@ -78,8 +78,8 @@ make a review look thorough is not.
|
||||
|
||||
Also out of scope in every review:
|
||||
|
||||
- Style and formatting a linter owns (RuboCop, ESLint, Prettier), unless it actively obscures
|
||||
meaning or breaks a documented standard.
|
||||
- Style and formatting a linter or formatter owns (RuboCop, ESLint, oxlint, Oxfmt, Stylelint),
|
||||
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,6 +308,19 @@ 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]`
|
||||
|
||||
@@ -9,7 +9,7 @@ Resolvers are thin wrappers. Business logic lives in `app/services/service/`:
|
||||
|
||||
```ruby
|
||||
def resolve(...)
|
||||
Service::SomeAction.new(...).execute
|
||||
Service::SomeAction.execute(...)
|
||||
end
|
||||
```
|
||||
|
||||
|
||||
@@ -64,5 +64,13 @@ 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.
|
||||
|
||||
Each of these carries the reasoning in a code comment; keep the two in sync.
|
||||
|
||||
@@ -36,8 +36,8 @@ is not enough.
|
||||
|
||||
### Skeleton
|
||||
|
||||
Minimal concern following `app/models/mention/assets.rb` and
|
||||
`app/models/audit_log/assets.rb` (belongs_to user + polymorphic relation):
|
||||
Minimal concern following `app/models/mention/assets.rb` (belongs_to user +
|
||||
polymorphic relation):
|
||||
|
||||
```ruby
|
||||
class MyModel
|
||||
@@ -67,7 +67,6 @@ 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
|
||||
|
||||
|
||||
@@ -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
|
||||
`zammad/coordination-desktop-view#785`; unclear whether it generalises.
|
||||
stored independently per view? Seen once, in the story that introduced editing knowledge base
|
||||
answers; 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? `#785` 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? The same story 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`.
|
||||
|
||||
@@ -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 — `zammad/coordination-desktop-view#785`
|
||||
### Worked example — editing knowledge base answers
|
||||
|
||||
"Edit an answer of the knowledge base", one story, sixteen tasks, one very large merge request.
|
||||
Its criteria classified by the test above:
|
||||
|
||||
@@ -40,6 +40,19 @@ 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
|
||||
@@ -79,11 +92,15 @@ Full reference:
|
||||
Test files use `.spec.ts` extension and are co-located with source files.
|
||||
|
||||
```bash
|
||||
VITE_TEST_MODE=1 pnpm test -- app/frontend/path/to/file.spec.ts
|
||||
pnpm test --run app/frontend/path/to/file.spec.ts
|
||||
```
|
||||
|
||||
Always pass `--run` and at least one file path. In an interactive terminal,
|
||||
watch mode is the default and does not exit. Never put `--` between `pnpm test` and the arguments: pnpm
|
||||
forwards it, Vitest then ignores everything after it and runs the complete
|
||||
suite. `VITE_TEST_MODE` is not needed, Vitest enables the test mode itself.
|
||||
|
||||
Tests use **Testing Library** (on top of Vue Test Utils).
|
||||
Watch mode is the default.
|
||||
|
||||
### Two levels of frontend tests
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
Two things from it deserve emphasis in this position specifically:
|
||||
Three 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.
|
||||
|
||||
@@ -39,6 +39,26 @@ 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.
|
||||
@@ -47,6 +67,13 @@ The CoffeeScript frontend uses REST controllers.
|
||||
- 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
|
||||
|
||||
@@ -62,12 +89,38 @@ bundle exec rubocop --autocorrect app/path/to/file.rb # L
|
||||
Always use pnpm for frontend and cross-stack commands.
|
||||
|
||||
```bash
|
||||
pnpm test app/frontend/path/to/file.spec.ts # Run specific Vitest test
|
||||
pnpm lint # Run all linters
|
||||
pnpm generate-graphql-api # Regenerate GraphQL types after schema changes
|
||||
pnpm generate-setting-types # Regenerate Config types after setting changes
|
||||
pnpm test --run app/frontend/path/to/file.spec.ts # Run specific Vitest test (never add `--`)
|
||||
pnpm lint # Run all linters
|
||||
pnpm generate-graphql-api # Regenerate GraphQL types after schema changes
|
||||
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**.
|
||||
@@ -78,7 +131,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 → Commit → Review → MR (`/create-mr`) → Cherry-pick
|
||||
Implement → Test → Review → Commit → MR (`/create-mr`) → Cherry-pick
|
||||
(`/cherry-pick-to-stable`)
|
||||
|
||||
See `.dev/agent_docs/development_workflow.md` for the full lifecycle and
|
||||
@@ -111,7 +164,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
|
||||
@@ -120,14 +173,9 @@ 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 — `codegraph` is a local dev dependency, so the bare
|
||||
command is not on `PATH`.
|
||||
prints the same output using the repository-pinned CodeGraph version.
|
||||
|
||||
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.
|
||||
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`.
|
||||
|
||||
@@ -20,7 +20,14 @@ export default defineConfig({
|
||||
downloadsFolder: '.dev/cypress/downloads',
|
||||
screenshotsFolder: '.dev/cypress/screenshots',
|
||||
videoCompression: false,
|
||||
env: {
|
||||
// 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: {
|
||||
CY_CI: isCYCI,
|
||||
pluginVisualRegressionDiffConfig: {
|
||||
threshold: 0.02,
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@frsource/cypress-plugin-visual-regression-diff": "^4.1.1",
|
||||
"@frsource/cypress-plugin-visual-regression-diff": "^4.2.1",
|
||||
"@testing-library/cypress": "^10.1.3",
|
||||
"cypress": "^15.21.1",
|
||||
"cypress-real-events": "^1.15.0"
|
||||
"cypress": "^16.1.1",
|
||||
"cypress-real-events": "^1.15.1"
|
||||
},
|
||||
"imports": {
|
||||
"#tests/*": "../../app/frontend/tests/*",
|
||||
|
||||
303
.dev/cypress/pnpm-lock.yaml
generated
303
.dev/cypress/pnpm-lock.yaml
generated
@@ -9,17 +9,17 @@ importers:
|
||||
.:
|
||||
devDependencies:
|
||||
'@frsource/cypress-plugin-visual-regression-diff':
|
||||
specifier: ^4.1.1
|
||||
version: 4.1.1(cypress@15.21.1)
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1(cypress@16.1.1)
|
||||
'@testing-library/cypress':
|
||||
specifier: ^10.1.3
|
||||
version: 10.1.3(cypress@15.21.1)
|
||||
version: 10.1.3(cypress@16.1.1)
|
||||
cypress:
|
||||
specifier: ^15.21.1
|
||||
version: 15.21.1
|
||||
specifier: ^16.1.1
|
||||
version: 16.1.1
|
||||
cypress-real-events:
|
||||
specifier: ^1.15.0
|
||||
version: 1.15.0(cypress@15.21.1)
|
||||
specifier: ^1.15.1
|
||||
version: 1.15.1(cypress@16.1.1)
|
||||
|
||||
packages:
|
||||
|
||||
@@ -45,12 +45,12 @@ packages:
|
||||
'@emnapi/runtime@1.11.3':
|
||||
resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==}
|
||||
|
||||
'@frsource/base64@1.0.213':
|
||||
resolution: {integrity: sha512-KaW0AbWO5gLtFJ+C9QZjY25P2dKkOFBtKtuyIj+DEA87moaO4cpovazCE8Zps8JP7cVyuZ+rDxkXlQTt8xli+Q==}
|
||||
'@frsource/base64@1.0.237':
|
||||
resolution: {integrity: sha512-X0tSa8rrusTEkYNy7aE+M+sZlsdyjMt8OgxWc6RerYefph3EKIVRTaL0qg8iw53iabvM7mJDK8MNc4W1yTAz2w==}
|
||||
|
||||
'@frsource/cypress-plugin-visual-regression-diff@4.1.1':
|
||||
resolution: {integrity: sha512-O/RAf8shfs9UcwzPfR0TmrXnuy0UymjPNrbLhXFsls2bHsnWH++LskqXgkMdi9fnTA7E51XKqptUexNKOeEg7g==}
|
||||
engines: {node: '>=14'}
|
||||
'@frsource/cypress-plugin-visual-regression-diff@4.2.1':
|
||||
resolution: {integrity: sha512-zu+Lfet2mydw+kkGMQ96gN6w8uejPsonKgmaybAMrS3HVXx4V28D961Q8f7ohs9hGa+v+njPJeGbTZuKpnLCkQ==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
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.3':
|
||||
resolution: {integrity: sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==}
|
||||
'@img/sharp-darwin-arm64@0.35.4':
|
||||
resolution: {integrity: sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@img/sharp-darwin-x64@0.35.3':
|
||||
resolution: {integrity: sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==}
|
||||
'@img/sharp-darwin-x64@0.35.4':
|
||||
resolution: {integrity: sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@img/sharp-freebsd-wasm32@0.35.3':
|
||||
resolution: {integrity: sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==}
|
||||
'@img/sharp-freebsd-wasm32@0.35.4':
|
||||
resolution: {integrity: sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
os: [freebsd]
|
||||
|
||||
'@img/sharp-libvips-darwin-arm64@1.3.2':
|
||||
resolution: {integrity: sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==}
|
||||
'@img/sharp-libvips-darwin-arm64@1.3.3':
|
||||
resolution: {integrity: sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@img/sharp-libvips-darwin-x64@1.3.2':
|
||||
resolution: {integrity: sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==}
|
||||
'@img/sharp-libvips-darwin-x64@1.3.3':
|
||||
resolution: {integrity: sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@img/sharp-libvips-linux-arm64@1.3.2':
|
||||
resolution: {integrity: sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==}
|
||||
'@img/sharp-libvips-linux-arm64@1.3.3':
|
||||
resolution: {integrity: sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-arm@1.3.2':
|
||||
resolution: {integrity: sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==}
|
||||
'@img/sharp-libvips-linux-arm@1.3.3':
|
||||
resolution: {integrity: sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-ppc64@1.3.2':
|
||||
resolution: {integrity: sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==}
|
||||
'@img/sharp-libvips-linux-ppc64@1.3.3':
|
||||
resolution: {integrity: sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-riscv64@1.3.2':
|
||||
resolution: {integrity: sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==}
|
||||
'@img/sharp-libvips-linux-riscv64@1.3.3':
|
||||
resolution: {integrity: sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-s390x@1.3.2':
|
||||
resolution: {integrity: sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==}
|
||||
'@img/sharp-libvips-linux-s390x@1.3.3':
|
||||
resolution: {integrity: sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-x64@1.3.2':
|
||||
resolution: {integrity: sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==}
|
||||
'@img/sharp-libvips-linux-x64@1.3.3':
|
||||
resolution: {integrity: sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.3.2':
|
||||
resolution: {integrity: sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==}
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.3.3':
|
||||
resolution: {integrity: sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.3.2':
|
||||
resolution: {integrity: sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==}
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.3.3':
|
||||
resolution: {integrity: sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linux-arm64@0.35.3':
|
||||
resolution: {integrity: sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==}
|
||||
'@img/sharp-linux-arm64@0.35.4':
|
||||
resolution: {integrity: sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-arm@0.35.3':
|
||||
resolution: {integrity: sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==}
|
||||
'@img/sharp-linux-arm@0.35.4':
|
||||
resolution: {integrity: sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-ppc64@0.35.3':
|
||||
resolution: {integrity: sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==}
|
||||
'@img/sharp-linux-ppc64@0.35.4':
|
||||
resolution: {integrity: sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-riscv64@0.35.3':
|
||||
resolution: {integrity: sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==}
|
||||
'@img/sharp-linux-riscv64@0.35.4':
|
||||
resolution: {integrity: sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-s390x@0.35.3':
|
||||
resolution: {integrity: sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==}
|
||||
'@img/sharp-linux-s390x@0.35.4':
|
||||
resolution: {integrity: sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-x64@0.35.3':
|
||||
resolution: {integrity: sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==}
|
||||
'@img/sharp-linux-x64@0.35.4':
|
||||
resolution: {integrity: sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.35.3':
|
||||
resolution: {integrity: sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==}
|
||||
'@img/sharp-linuxmusl-arm64@0.35.4':
|
||||
resolution: {integrity: sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.35.3':
|
||||
resolution: {integrity: sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==}
|
||||
'@img/sharp-linuxmusl-x64@0.35.4':
|
||||
resolution: {integrity: sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-wasm32@0.35.3':
|
||||
resolution: {integrity: sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==}
|
||||
'@img/sharp-wasm32@0.35.4':
|
||||
resolution: {integrity: sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
|
||||
'@img/sharp-webcontainers-wasm32@0.35.3':
|
||||
resolution: {integrity: sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==}
|
||||
'@img/sharp-webcontainers-wasm32@0.35.4':
|
||||
resolution: {integrity: sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [wasm32]
|
||||
|
||||
'@img/sharp-win32-arm64@0.35.3':
|
||||
resolution: {integrity: sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==}
|
||||
'@img/sharp-win32-arm64@0.35.4':
|
||||
resolution: {integrity: sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@img/sharp-win32-ia32@0.35.3':
|
||||
resolution: {integrity: sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==}
|
||||
'@img/sharp-win32-ia32@0.35.4':
|
||||
resolution: {integrity: sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==}
|
||||
engines: {node: ^20.9.0}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@img/sharp-win32-x64@0.35.3':
|
||||
resolution: {integrity: sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==}
|
||||
'@img/sharp-win32-x64@0.35.4':
|
||||
resolution: {integrity: sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==}
|
||||
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.0:
|
||||
resolution: {integrity: sha512-in98xxTnnM9Z7lZBvvVozm99PBT2eEOjXRG5LKWyYvQnj9mGWXMiPNpfw7e7WiraBFh7XlXIxnE9Cu5o+52kQQ==}
|
||||
cypress-real-events@1.15.1:
|
||||
resolution: {integrity: sha512-BiTyqYcNHiIFWfSLRn3H+TgTvpnTGOKfl4Brz51yeSGKMxBbD60wtRQw31nII00yQ67Xg3C18mU8lqUxDN3OUA==}
|
||||
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
|
||||
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@15.21.1:
|
||||
resolution: {integrity: sha512-ogHpHMj0XNlZA5MGzjg8SWHf0eMw9lTwVQRKjpcT1GzNTxNUjwnHA8YCG6nOJ8ThU+XZaUxJgpMYZnJ//8aDaw==}
|
||||
engines: {node: ^20.1.0 || ^22.0.0 || >=24.0.0}
|
||||
cypress@16.1.1:
|
||||
resolution: {integrity: sha512-T09qNpMKssD6eEMqqfywQ8OUub/VrNCvIUFAyQCfWsJ6o4CZ5Yeapce+WoZY9FXly6+3+pk7sjEB5voxduWQlQ==}
|
||||
engines: {node: ^22.0.0 || ^24.0.0 || >=26.0.0}
|
||||
hasBin: true
|
||||
|
||||
dashdash@1.14.1:
|
||||
resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
||||
dayjs@1.11.21:
|
||||
resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==}
|
||||
dayjs@1.11.23:
|
||||
resolution: {integrity: sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==}
|
||||
|
||||
debug@3.2.7:
|
||||
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
||||
@@ -490,7 +490,6 @@ 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==}
|
||||
@@ -625,7 +624,6 @@ 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==}
|
||||
@@ -794,8 +792,8 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
sharp@0.35.3:
|
||||
resolution: {integrity: sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==}
|
||||
sharp@0.35.4:
|
||||
resolution: {integrity: sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
peerDependencies:
|
||||
'@types/node': '*'
|
||||
@@ -930,7 +928,6 @@ 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==}
|
||||
@@ -1004,132 +1001,132 @@ snapshots:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@frsource/base64@1.0.213': {}
|
||||
'@frsource/base64@1.0.237': {}
|
||||
|
||||
'@frsource/cypress-plugin-visual-regression-diff@4.1.1(cypress@15.21.1)':
|
||||
'@frsource/cypress-plugin-visual-regression-diff@4.2.1(cypress@16.1.1)':
|
||||
dependencies:
|
||||
'@frsource/base64': 1.0.213
|
||||
cypress: 15.21.1
|
||||
'@frsource/base64': 1.0.237
|
||||
cypress: 16.1.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.3
|
||||
sharp: 0.35.4
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
|
||||
'@img/colour@1.1.0': {}
|
||||
|
||||
'@img/sharp-darwin-arm64@0.35.3':
|
||||
'@img/sharp-darwin-arm64@0.35.4':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-darwin-arm64': 1.3.2
|
||||
'@img/sharp-libvips-darwin-arm64': 1.3.3
|
||||
optional: true
|
||||
|
||||
'@img/sharp-darwin-x64@0.35.3':
|
||||
'@img/sharp-darwin-x64@0.35.4':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-darwin-x64': 1.3.2
|
||||
'@img/sharp-libvips-darwin-x64': 1.3.3
|
||||
optional: true
|
||||
|
||||
'@img/sharp-freebsd-wasm32@0.35.3':
|
||||
'@img/sharp-freebsd-wasm32@0.35.4':
|
||||
dependencies:
|
||||
'@img/sharp-wasm32': 0.35.3
|
||||
'@img/sharp-wasm32': 0.35.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-darwin-arm64@1.3.2':
|
||||
'@img/sharp-libvips-darwin-arm64@1.3.3':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-darwin-x64@1.3.2':
|
||||
'@img/sharp-libvips-darwin-x64@1.3.3':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-arm64@1.3.2':
|
||||
'@img/sharp-libvips-linux-arm64@1.3.3':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-arm@1.3.2':
|
||||
'@img/sharp-libvips-linux-arm@1.3.3':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-ppc64@1.3.2':
|
||||
'@img/sharp-libvips-linux-ppc64@1.3.3':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-riscv64@1.3.2':
|
||||
'@img/sharp-libvips-linux-riscv64@1.3.3':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-s390x@1.3.2':
|
||||
'@img/sharp-libvips-linux-s390x@1.3.3':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-x64@1.3.2':
|
||||
'@img/sharp-libvips-linux-x64@1.3.3':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.3.2':
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.3.3':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.3.2':
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.3.3':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-arm64@0.35.3':
|
||||
'@img/sharp-linux-arm64@0.35.4':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-arm64': 1.3.2
|
||||
'@img/sharp-libvips-linux-arm64': 1.3.3
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-arm@0.35.3':
|
||||
'@img/sharp-linux-arm@0.35.4':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-arm': 1.3.2
|
||||
'@img/sharp-libvips-linux-arm': 1.3.3
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-ppc64@0.35.3':
|
||||
'@img/sharp-linux-ppc64@0.35.4':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-ppc64': 1.3.2
|
||||
'@img/sharp-libvips-linux-ppc64': 1.3.3
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-riscv64@0.35.3':
|
||||
'@img/sharp-linux-riscv64@0.35.4':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-riscv64': 1.3.2
|
||||
'@img/sharp-libvips-linux-riscv64': 1.3.3
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-s390x@0.35.3':
|
||||
'@img/sharp-linux-s390x@0.35.4':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-s390x': 1.3.2
|
||||
'@img/sharp-libvips-linux-s390x': 1.3.3
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-x64@0.35.3':
|
||||
'@img/sharp-linux-x64@0.35.4':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-x64': 1.3.2
|
||||
'@img/sharp-libvips-linux-x64': 1.3.3
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.35.3':
|
||||
'@img/sharp-linuxmusl-arm64@0.35.4':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linuxmusl-arm64': 1.3.2
|
||||
'@img/sharp-libvips-linuxmusl-arm64': 1.3.3
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.35.3':
|
||||
'@img/sharp-linuxmusl-x64@0.35.4':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linuxmusl-x64': 1.3.2
|
||||
'@img/sharp-libvips-linuxmusl-x64': 1.3.3
|
||||
optional: true
|
||||
|
||||
'@img/sharp-wasm32@0.35.3':
|
||||
'@img/sharp-wasm32@0.35.4':
|
||||
dependencies:
|
||||
'@emnapi/runtime': 1.11.3
|
||||
optional: true
|
||||
|
||||
'@img/sharp-webcontainers-wasm32@0.35.3':
|
||||
'@img/sharp-webcontainers-wasm32@0.35.4':
|
||||
dependencies:
|
||||
'@img/sharp-wasm32': 0.35.3
|
||||
'@img/sharp-wasm32': 0.35.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-arm64@0.35.3':
|
||||
'@img/sharp-win32-arm64@0.35.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-ia32@0.35.3':
|
||||
'@img/sharp-win32-ia32@0.35.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-x64@0.35.3':
|
||||
'@img/sharp-win32-x64@0.35.4':
|
||||
optional: true
|
||||
|
||||
'@testing-library/cypress@10.1.3(cypress@15.21.1)':
|
||||
'@testing-library/cypress@10.1.3(cypress@16.1.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.7
|
||||
'@testing-library/dom': 10.4.1
|
||||
cypress: 15.21.1
|
||||
cypress: 16.1.1
|
||||
|
||||
'@testing-library/dom@10.4.1':
|
||||
dependencies:
|
||||
@@ -1280,11 +1277,11 @@ snapshots:
|
||||
shebang-command: 2.0.0
|
||||
which: 2.0.2
|
||||
|
||||
cypress-real-events@1.15.0(cypress@15.21.1):
|
||||
cypress-real-events@1.15.1(cypress@16.1.1):
|
||||
dependencies:
|
||||
cypress: 15.21.1
|
||||
cypress: 16.1.1
|
||||
|
||||
cypress@15.21.1:
|
||||
cypress@16.1.1:
|
||||
dependencies:
|
||||
'@cypress/request': 4.0.1
|
||||
'@cypress/xvfb': 1.2.4(supports-color@8.1.1)
|
||||
@@ -1302,7 +1299,7 @@ snapshots:
|
||||
cli-table3: 0.6.1
|
||||
commander: 6.2.1
|
||||
common-tags: 1.8.2
|
||||
dayjs: 1.11.21
|
||||
dayjs: 1.11.23
|
||||
debug: 4.4.3(supports-color@8.1.1)
|
||||
eventemitter2: 6.4.7
|
||||
execa: 4.1.0
|
||||
@@ -1333,7 +1330,7 @@ snapshots:
|
||||
dependencies:
|
||||
assert-plus: 1.0.0
|
||||
|
||||
dayjs@1.11.21: {}
|
||||
dayjs@1.11.23: {}
|
||||
|
||||
debug@3.2.7(supports-color@8.1.1):
|
||||
dependencies:
|
||||
@@ -1689,37 +1686,37 @@ snapshots:
|
||||
|
||||
semver@7.8.5: {}
|
||||
|
||||
sharp@0.35.3:
|
||||
sharp@0.35.4:
|
||||
dependencies:
|
||||
'@img/colour': 1.1.0
|
||||
detect-libc: 2.1.2
|
||||
semver: 7.8.5
|
||||
optionalDependencies:
|
||||
'@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
|
||||
'@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
|
||||
|
||||
shebang-command@2.0.0:
|
||||
dependencies:
|
||||
|
||||
@@ -16,7 +16,7 @@ globalThis.process.env = {
|
||||
|
||||
Cypress.Screenshot.defaults({ capture: 'viewport' })
|
||||
|
||||
if (Cypress.env('CY_CI')) {
|
||||
if (Cypress.expose('CY_CI')) {
|
||||
Cypress.config('defaultCommandTimeout', 20000)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,4 +4,4 @@ corepack enable pnpm
|
||||
|
||||
pnpm install --frozen-lockfile --ignore-scripts
|
||||
pnpm cypress:install
|
||||
pnpm test:ci:ct --env pluginVisualRegressionUpdateImages=$CYPRESS_UPDATE_SNAPSHOTS --spec '../../**/*-visuals.cy.*'
|
||||
pnpm test:ci:ct --expose pluginVisualRegressionUpdateImages=$CYPRESS_UPDATE_SNAPSHOTS --spec '../../**/*-visuals.cy.*'
|
||||
|
||||
@@ -32,7 +32,7 @@ services:
|
||||
command: sleep infinity
|
||||
|
||||
elasticsearch:
|
||||
image: elasticsearch:9.3.2
|
||||
image: elasticsearch:9.5.3
|
||||
environment:
|
||||
- "ES_JAVA_OPTS=-Xms1024m -Xmx1024m"
|
||||
- discovery.type=single-node
|
||||
@@ -56,7 +56,7 @@ services:
|
||||
|
||||
memcached:
|
||||
command: memcached -m 256M
|
||||
image: memcached:1.6.38-alpine
|
||||
image: memcached:1.6.45-alpine
|
||||
restart: unless-stopped
|
||||
|
||||
postgresql:
|
||||
|
||||
@@ -13,9 +13,9 @@ pnpm config set store-dir "$(pwd)/.pnpm-store" --global
|
||||
|
||||
bin/setup --skip-server
|
||||
|
||||
echo "== Install CodeGraph index =="
|
||||
echo "== Initialize CodeGraph index =="
|
||||
|
||||
pnpm run codegraph:install
|
||||
pnpm run codegraph:init
|
||||
|
||||
echo "== Precompile Ruby cache =="
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@ pnpm config set store-dir "$(pwd)/.pnpm-store" --global
|
||||
|
||||
bin/setup --skip-server
|
||||
|
||||
echo "== Install CodeGraph index =="
|
||||
echo "== Initialize CodeGraph index =="
|
||||
|
||||
pnpm run codegraph:install
|
||||
pnpm run codegraph:init
|
||||
|
||||
echo "== Precompile Ruby cache =="
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@ pnpm config set store-dir "$(pwd)/.pnpm-store" --global
|
||||
|
||||
bin/setup --skip-server
|
||||
|
||||
echo "== Install CodeGraph index =="
|
||||
echo "== Initialize CodeGraph index =="
|
||||
|
||||
pnpm run codegraph:install
|
||||
pnpm run codegraph:init
|
||||
|
||||
echo "== Precompile Ruby cache =="
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@ pnpm config set store-dir "$(pwd)/.pnpm-store" --global
|
||||
|
||||
bin/setup --skip-server
|
||||
|
||||
echo "== Install CodeGraph index =="
|
||||
echo "== Initialize CodeGraph index =="
|
||||
|
||||
pnpm run codegraph:install
|
||||
pnpm run codegraph:init
|
||||
|
||||
echo "== Precompile Ruby cache =="
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
"requireindex": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^10.9.1",
|
||||
"eslint": "^10.12.0",
|
||||
"eslint-plugin-eslint-plugin": "^7.6.2",
|
||||
"eslint-plugin-n": "^18.3.0",
|
||||
"mocha": "^11.8.0",
|
||||
"eslint-plugin-n": "^18.4.0",
|
||||
"mocha": "^12.0.3",
|
||||
"vue-eslint-parser": "^10.4.1"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
604
.eslint-plugin-zammad/pnpm-lock.yaml
generated
604
.eslint-plugin-zammad/pnpm-lock.yaml
generated
@@ -13,23 +13,29 @@ importers:
|
||||
version: 1.2.0
|
||||
devDependencies:
|
||||
eslint:
|
||||
specifier: ^10.9.1
|
||||
version: 10.9.1(supports-color@8.1.1)
|
||||
specifier: ^10.12.0
|
||||
version: 10.12.0(supports-color@8.1.1)
|
||||
eslint-plugin-eslint-plugin:
|
||||
specifier: ^7.6.2
|
||||
version: 7.6.2(eslint@10.9.1(supports-color@8.1.1))
|
||||
version: 7.6.2(eslint@10.12.0(supports-color@8.1.1))
|
||||
eslint-plugin-n:
|
||||
specifier: ^18.3.0
|
||||
version: 18.3.0(eslint@10.9.1(supports-color@8.1.1))
|
||||
specifier: ^18.4.0
|
||||
version: 18.4.0(eslint@10.12.0(supports-color@8.1.1))
|
||||
mocha:
|
||||
specifier: ^11.8.0
|
||||
version: 11.8.0
|
||||
specifier: ^12.0.3
|
||||
version: 12.0.3
|
||||
vue-eslint-parser:
|
||||
specifier: ^10.4.1
|
||||
version: 10.4.1(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1)
|
||||
version: 10.4.1(eslint@10.12.0(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}
|
||||
@@ -56,8 +62,8 @@ packages:
|
||||
resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
|
||||
'@eslint/plugin-kit@0.7.2':
|
||||
resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==}
|
||||
'@eslint/plugin-kit@0.7.3':
|
||||
resolution: {integrity: sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
|
||||
'@humanfs/core@0.19.2':
|
||||
@@ -80,13 +86,14 @@ packages:
|
||||
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
|
||||
engines: {node: '>=18.18'}
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||
engines: {node: '>=12'}
|
||||
'@keyv/bigmap@1.3.1':
|
||||
resolution: {integrity: sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==}
|
||||
engines: {node: '>= 18'}
|
||||
peerDependencies:
|
||||
keyv: ^5.6.0
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
'@keyv/serialize@1.1.1':
|
||||
resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==}
|
||||
|
||||
'@types/esrecurse@4.3.1':
|
||||
resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==}
|
||||
@@ -110,35 +117,13 @@ 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}
|
||||
@@ -146,28 +131,12 @@ packages:
|
||||
browser-stdout@1.3.1:
|
||||
resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==}
|
||||
|
||||
camelcase@6.3.0:
|
||||
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
|
||||
engines: {node: '>=10'}
|
||||
cacheable@2.5.0:
|
||||
resolution: {integrity: sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==}
|
||||
|
||||
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==}
|
||||
chokidar@5.0.0:
|
||||
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
|
||||
engines: {node: '>= 20.19.0'}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
@@ -182,34 +151,17 @@ 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@7.0.0:
|
||||
resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==}
|
||||
diff@9.0.0:
|
||||
resolution: {integrity: sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==}
|
||||
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'}
|
||||
@@ -232,8 +184,8 @@ packages:
|
||||
peerDependencies:
|
||||
eslint: '>=9.0.0'
|
||||
|
||||
eslint-plugin-n@18.3.0:
|
||||
resolution: {integrity: sha512-cPVguuDe6DrIPb/qUXHf8P89MaVTUmiYWwpt5gX5AILsvRIiZAxMFXcFR6QHYBksqKJpjfUBlL/RleCJUWcD7w==}
|
||||
eslint-plugin-n@18.4.0:
|
||||
resolution: {integrity: sha512-TEm6Nqn9+l7EWfJXOL6pmrEdpiM2ZTgo4fNMRDvBTM70jeQ7x3X3Akcx+sRCIRfpcSVwMypH3ZAA33PLyNKmYw==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
peerDependencies:
|
||||
eslint: '>=8.57.1'
|
||||
@@ -257,8 +209,8 @@ packages:
|
||||
resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
|
||||
eslint@10.9.1:
|
||||
resolution: {integrity: sha512-9VaAkDURekixUQJy0oJYl2DcN6oKMfxay7XzaGYAWQwsb6qfKf+x76R2k1L8kb1boc+FyCAaTA9GmiKaaiaF+A==}
|
||||
eslint@10.12.0:
|
||||
resolution: {integrity: sha512-npHjrHb2o4ehH3mE+YuxABR+0gyb0aVWCIolgJDArwCiHEIsonBm3ZKjel5b0U5+a+MbEvIyRCGCzD3eKKK2NA==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -296,33 +248,23 @@ packages:
|
||||
fast-levenshtein@2.0.6:
|
||||
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
|
||||
|
||||
file-entry-cache@8.0.0:
|
||||
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
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'}
|
||||
|
||||
find-up@5.0.0:
|
||||
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
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
|
||||
flat-cache@6.1.23:
|
||||
resolution: {integrity: sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==}
|
||||
|
||||
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==}
|
||||
|
||||
@@ -330,10 +272,9 @@ packages:
|
||||
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
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
|
||||
glob@13.0.6:
|
||||
resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
globals@15.15.0:
|
||||
resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==}
|
||||
@@ -349,9 +290,15 @@ packages:
|
||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
he@1.2.0:
|
||||
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
|
||||
hasBin: true
|
||||
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==}
|
||||
|
||||
ignore@5.3.2:
|
||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||
@@ -365,21 +312,13 @@ 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@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-path-inside@4.0.0:
|
||||
resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
is-unicode-supported@0.1.0:
|
||||
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
|
||||
@@ -388,24 +327,18 @@ packages:
|
||||
isexe@2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
|
||||
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==}
|
||||
js-yaml@5.4.1:
|
||||
resolution: {integrity: sha512-28R/k+NAjeuf7+CKlTxWZVExJGwVVLwY06DgEnOMz2gEpfNkDcD7QvyiVPT0xy0XXhU8vHsd4Ot42OOPdJG7dQ==}
|
||||
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@4.5.4:
|
||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||
keyv@5.6.0:
|
||||
resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==}
|
||||
|
||||
levn@0.4.1:
|
||||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||
@@ -415,28 +348,21 @@ packages:
|
||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
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==}
|
||||
lru-cache@11.5.2:
|
||||
resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
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@11.8.0:
|
||||
resolution: {integrity: sha512-VyCeUdGN3A9lmCTTgG4yuvY9ixxaDk+xt2R/7/+1AP6EqNG+G9OKkzBwhVtVYoNX8YsxNSgAl8mOv3IAeOpFbw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
mocha@12.0.3:
|
||||
resolution: {integrity: sha512-beammieDdiWdkjANWeWPpkxUROYGi5FN88fPOkLiticz7HTg3Y+Pho3EYE6SUdcuWnZ3h2WD8EXCA55JF4o6wg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
|
||||
ms@2.1.3:
|
||||
@@ -457,9 +383,6 @@ 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'}
|
||||
@@ -468,9 +391,9 @@ packages:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
path-scurry@1.11.1:
|
||||
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
|
||||
engines: {node: '>=16 || 14 >=14.18'}
|
||||
path-scurry@2.0.2:
|
||||
resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
@@ -483,16 +406,13 @@ packages:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
randombytes@2.1.0:
|
||||
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
|
||||
qified@0.10.1:
|
||||
resolution: {integrity: sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
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'}
|
||||
readdirp@5.1.1:
|
||||
resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==}
|
||||
engines: {node: '>= 20.19.0'}
|
||||
|
||||
requireindex@1.2.0:
|
||||
resolution: {integrity: sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==}
|
||||
@@ -501,16 +421,14 @@ 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@6.0.2:
|
||||
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
|
||||
serialize-javascript@7.1.1:
|
||||
resolution: {integrity: sha512-k3CMsaIvvdSwm8oLB4MXSl0wH2/cwlH7xGcnRd2DaeRmBkbzYmyT8j0tsX60DwD1eRwHTpNpH8ljKu9oUT1MeQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
@@ -520,33 +438,9 @@ packages:
|
||||
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
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'}
|
||||
strip-json-comments@5.0.3:
|
||||
resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
||||
supports-color@8.1.1:
|
||||
resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
|
||||
@@ -578,32 +472,8 @@ packages:
|
||||
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
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'}
|
||||
workerpool@10.0.3:
|
||||
resolution: {integrity: sha512-6z2Iis68Wqth93/G/wJP9u+R3O+d2XTlgWChGCwuT1qLbBsOYueGRZuJ++v3mtDP5KjYdy+WzvWC+VWETSVXJA==}
|
||||
|
||||
yocto-queue@0.1.0:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
@@ -611,9 +481,21 @@ packages:
|
||||
|
||||
snapshots:
|
||||
|
||||
'@eslint-community/eslint-utils@4.10.1(eslint@10.9.1(supports-color@8.1.1))':
|
||||
'@cacheable/memory@2.2.0':
|
||||
dependencies:
|
||||
eslint: 10.9.1(supports-color@8.1.1)
|
||||
'@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-visitor-keys: 3.4.3
|
||||
|
||||
'@eslint-community/regexpp@4.12.2': {}
|
||||
@@ -636,7 +518,7 @@ snapshots:
|
||||
|
||||
'@eslint/object-schema@3.0.5': {}
|
||||
|
||||
'@eslint/plugin-kit@0.7.2':
|
||||
'@eslint/plugin-kit@0.7.3':
|
||||
dependencies:
|
||||
'@eslint/core': 1.2.1
|
||||
levn: 0.4.1
|
||||
@@ -657,17 +539,13 @@ snapshots:
|
||||
|
||||
'@humanwhocodes/retry@0.4.3': {}
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
'@keyv/bigmap@1.3.1(keyv@5.6.0)':
|
||||
dependencies:
|
||||
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
|
||||
hashery: 1.5.1
|
||||
hookified: 1.15.1
|
||||
keyv: 5.6.0
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
'@keyv/serialize@1.1.1': {}
|
||||
|
||||
'@types/esrecurse@4.3.1': {}
|
||||
|
||||
@@ -688,54 +566,27 @@ 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: {}
|
||||
|
||||
camelcase@6.3.0: {}
|
||||
|
||||
chalk@4.1.2:
|
||||
cacheable@2.5.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
supports-color: 7.2.0
|
||||
'@cacheable/memory': 2.2.0
|
||||
'@cacheable/utils': 2.5.0
|
||||
hookified: 1.15.1
|
||||
keyv: 5.6.0
|
||||
qified: 0.10.1
|
||||
|
||||
chokidar@4.0.3:
|
||||
chokidar@5.0.0:
|
||||
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: {}
|
||||
readdirp: 5.1.1
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
@@ -749,55 +600,46 @@ snapshots:
|
||||
optionalDependencies:
|
||||
supports-color: 8.1.1
|
||||
|
||||
decamelize@4.0.0: {}
|
||||
|
||||
deep-is@0.1.4: {}
|
||||
|
||||
diff@7.0.0: {}
|
||||
|
||||
eastasianwidth@0.2.0: {}
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
|
||||
emoji-regex@9.2.2: {}
|
||||
diff@9.0.0: {}
|
||||
|
||||
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.9.1(supports-color@8.1.1)):
|
||||
eslint-compat-utils@0.5.1(eslint@10.12.0(supports-color@8.1.1)):
|
||||
dependencies:
|
||||
eslint: 10.9.1(supports-color@8.1.1)
|
||||
eslint: 10.12.0(supports-color@8.1.1)
|
||||
semver: 7.8.5
|
||||
|
||||
eslint-plugin-es-x@7.8.0(eslint@10.9.1(supports-color@8.1.1)):
|
||||
eslint-plugin-es-x@7.8.0(eslint@10.12.0(supports-color@8.1.1)):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.10.1(eslint@10.9.1(supports-color@8.1.1))
|
||||
'@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(supports-color@8.1.1))
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
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: 10.12.0(supports-color@8.1.1)
|
||||
eslint-compat-utils: 0.5.1(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)):
|
||||
eslint-plugin-eslint-plugin@7.6.2(eslint@10.12.0(supports-color@8.1.1)):
|
||||
dependencies:
|
||||
'@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)
|
||||
'@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)
|
||||
estraverse: 5.3.0
|
||||
|
||||
eslint-plugin-n@18.3.0(eslint@10.9.1(supports-color@8.1.1)):
|
||||
eslint-plugin-n@18.4.0(eslint@10.12.0(supports-color@8.1.1)):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.10.1(eslint@10.9.1(supports-color@8.1.1))
|
||||
'@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(supports-color@8.1.1))
|
||||
enhanced-resolve: 5.24.5
|
||||
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))
|
||||
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))
|
||||
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:
|
||||
@@ -811,14 +653,14 @@ snapshots:
|
||||
|
||||
eslint-visitor-keys@5.0.1: {}
|
||||
|
||||
eslint@10.9.1(supports-color@8.1.1):
|
||||
eslint@10.12.0(supports-color@8.1.1):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.10.1(eslint@10.9.1(supports-color@8.1.1))
|
||||
'@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(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.2
|
||||
'@eslint/plugin-kit': 0.7.3
|
||||
'@humanfs/node': 0.16.8
|
||||
'@humanwhocodes/module-importer': 1.0.1
|
||||
'@humanwhocodes/retry': 0.4.3
|
||||
@@ -833,7 +675,7 @@ snapshots:
|
||||
esquery: 1.7.0
|
||||
esutils: 2.0.3
|
||||
fast-deep-equal: 3.1.3
|
||||
file-entry-cache: 8.0.0
|
||||
file-entry-cache: 11.1.5
|
||||
find-up: 5.0.0
|
||||
glob-parent: 6.0.2
|
||||
ignore: 5.3.2
|
||||
@@ -870,31 +712,25 @@ snapshots:
|
||||
|
||||
fast-levenshtein@2.0.6: {}
|
||||
|
||||
file-entry-cache@8.0.0:
|
||||
file-entry-cache@11.1.5:
|
||||
dependencies:
|
||||
flat-cache: 4.0.1
|
||||
flat-cache: 6.1.23
|
||||
|
||||
find-up-simple@1.0.1: {}
|
||||
|
||||
find-up@5.0.0:
|
||||
dependencies:
|
||||
locate-path: 6.0.0
|
||||
path-exists: 4.0.0
|
||||
|
||||
flat-cache@4.0.1:
|
||||
flat-cache@6.1.23:
|
||||
dependencies:
|
||||
cacheable: 2.5.0
|
||||
flatted: 3.4.4
|
||||
keyv: 4.5.4
|
||||
|
||||
flat@5.0.2: {}
|
||||
hookified: 1.15.1
|
||||
|
||||
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
|
||||
@@ -903,14 +739,11 @@ snapshots:
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
|
||||
glob@10.5.0:
|
||||
glob@13.0.6:
|
||||
dependencies:
|
||||
foreground-child: 3.3.1
|
||||
jackspeak: 3.4.3
|
||||
minimatch: 9.0.9
|
||||
minimatch: 10.2.6
|
||||
minipass: 7.1.3
|
||||
package-json-from-dist: 1.0.1
|
||||
path-scurry: 1.11.1
|
||||
path-scurry: 2.0.2
|
||||
|
||||
globals@15.15.0: {}
|
||||
|
||||
@@ -920,7 +753,13 @@ snapshots:
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
|
||||
he@1.2.0: {}
|
||||
hashery@1.5.1:
|
||||
dependencies:
|
||||
hookified: 1.15.1
|
||||
|
||||
hookified@1.15.1: {}
|
||||
|
||||
hookified@2.2.0: {}
|
||||
|
||||
ignore@5.3.2: {}
|
||||
|
||||
@@ -928,39 +767,27 @@ 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@3.0.3: {}
|
||||
|
||||
is-plain-obj@2.1.0: {}
|
||||
is-path-inside@4.0.0: {}
|
||||
|
||||
is-unicode-supported@0.1.0: {}
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
||||
jackspeak@3.4.3:
|
||||
dependencies:
|
||||
'@isaacs/cliui': 8.0.2
|
||||
optionalDependencies:
|
||||
'@pkgjs/parseargs': 0.11.0
|
||||
|
||||
js-yaml@4.3.1:
|
||||
js-yaml@5.4.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@4.5.4:
|
||||
keyv@5.6.0:
|
||||
dependencies:
|
||||
json-buffer: 3.0.1
|
||||
'@keyv/serialize': 1.1.1
|
||||
|
||||
levn@0.4.1:
|
||||
dependencies:
|
||||
@@ -971,46 +798,32 @@ snapshots:
|
||||
dependencies:
|
||||
p-locate: 5.0.0
|
||||
|
||||
log-symbols@4.1.0:
|
||||
dependencies:
|
||||
chalk: 4.1.2
|
||||
is-unicode-supported: 0.1.0
|
||||
|
||||
lru-cache@10.4.3: {}
|
||||
lru-cache@11.5.2: {}
|
||||
|
||||
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@11.8.0:
|
||||
mocha@12.0.3:
|
||||
dependencies:
|
||||
browser-stdout: 1.3.1
|
||||
chokidar: 4.0.3
|
||||
chokidar: 5.0.0
|
||||
debug: 4.4.3(supports-color@8.1.1)
|
||||
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
|
||||
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
|
||||
ms: 2.1.3
|
||||
picocolors: 1.1.1
|
||||
serialize-javascript: 6.0.2
|
||||
strip-json-comments: 3.1.1
|
||||
serialize-javascript: 7.1.1
|
||||
strip-json-comments: 5.0.3
|
||||
supports-color: 8.1.1
|
||||
workerpool: 9.3.4
|
||||
yargs: 17.7.3
|
||||
yargs-parser: 21.1.1
|
||||
yargs-unparser: 2.0.0
|
||||
workerpool: 10.0.3
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
@@ -1033,15 +846,13 @@ 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@1.11.1:
|
||||
path-scurry@2.0.2:
|
||||
dependencies:
|
||||
lru-cache: 10.4.3
|
||||
lru-cache: 11.5.2
|
||||
minipass: 7.1.3
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
@@ -1050,25 +861,19 @@ snapshots:
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
randombytes@2.1.0:
|
||||
qified@0.10.1:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
hookified: 2.2.0
|
||||
|
||||
readdirp@4.1.2: {}
|
||||
|
||||
require-directory@2.1.1: {}
|
||||
readdirp@5.1.1: {}
|
||||
|
||||
requireindex@1.2.0: {}
|
||||
|
||||
resolve-pkg-maps@1.0.0: {}
|
||||
|
||||
safe-buffer@5.2.1: {}
|
||||
|
||||
semver@7.8.5: {}
|
||||
|
||||
serialize-javascript@6.0.2:
|
||||
dependencies:
|
||||
randombytes: 2.1.0
|
||||
serialize-javascript@7.1.1: {}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
dependencies:
|
||||
@@ -1076,33 +881,7 @@ snapshots:
|
||||
|
||||
shebang-regex@3.0.0: {}
|
||||
|
||||
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
|
||||
strip-json-comments@5.0.3: {}
|
||||
|
||||
supports-color@8.1.1:
|
||||
dependencies:
|
||||
@@ -1118,10 +897,10 @@ snapshots:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
vue-eslint-parser@10.4.1(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1):
|
||||
vue-eslint-parser@10.4.1(eslint@10.12.0(supports-color@8.1.1))(supports-color@8.1.1):
|
||||
dependencies:
|
||||
debug: 4.4.3(supports-color@8.1.1)
|
||||
eslint: 10.9.1(supports-color@8.1.1)
|
||||
eslint: 10.12.0(supports-color@8.1.1)
|
||||
eslint-scope: 9.1.2
|
||||
eslint-visitor-keys: 5.0.1
|
||||
espree: 11.2.0
|
||||
@@ -1136,39 +915,6 @@ snapshots:
|
||||
|
||||
word-wrap@1.2.5: {}
|
||||
|
||||
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
|
||||
workerpool@10.0.3: {}
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
|
||||
1
.github/workflows/packager.io.yaml
vendored
1
.github/workflows/packager.io.yaml
vendored
@@ -22,7 +22,6 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- debian:11
|
||||
- debian:12
|
||||
- debian:13
|
||||
- el:9
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -107,6 +107,7 @@
|
||||
|
||||
# Node
|
||||
**/node_modules
|
||||
/.vitest
|
||||
yarn-error.log
|
||||
.yarn/cache
|
||||
/.pnpm-store
|
||||
@@ -123,6 +124,8 @@ schema.graphql
|
||||
|
||||
# Packages
|
||||
*.zpm
|
||||
!.gitlab/docker/*.zpm
|
||||
/packages/
|
||||
|
||||
# Devcontainer
|
||||
/.devcontainer/with-mailserver/certs/*
|
||||
@@ -139,6 +142,6 @@ schema.graphql
|
||||
/.claude/skills
|
||||
/.claude/settings.local.json
|
||||
|
||||
# Codegraph
|
||||
# CodeGraph
|
||||
/.codegraph/
|
||||
/.mcp.json
|
||||
|
||||
90
.gitlab/check_native_gem_platforms.rb
Executable file
90
.gitlab/check_native_gem_platforms.rb
Executable file
@@ -0,0 +1,90 @@
|
||||
#!/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__))
|
||||
5
.gitlab/ci/__includes__/artifact_dependencies.yml
Normal file
5
.gitlab/ci/__includes__/artifact_dependencies.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
# 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: []
|
||||
@@ -4,9 +4,6 @@ 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
|
||||
@@ -18,16 +15,20 @@ 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]
|
||||
|
||||
48
.gitlab/ci/__includes__/job_needs.yml
Normal file
48
.gitlab/ci/__includes__/job_needs.yml
Normal file
@@ -0,0 +1,48 @@
|
||||
# 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
|
||||
@@ -1,5 +1,65 @@
|
||||
.job_rules:
|
||||
# Allow filtering CI pipelines with JOB_FILTER and BROWSER_TEST_FILTER push variabales.
|
||||
# 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.
|
||||
variable_filters:
|
||||
- if: $BROWSER_TEST_FILTER
|
||||
when: never
|
||||
@@ -8,22 +68,51 @@
|
||||
- if: $JOB_FILTER && $JOB_FILTER != $CI_JOB_NAME
|
||||
when: never
|
||||
|
||||
.job_rules_default:
|
||||
rules:
|
||||
- !reference [.job_rules, variable_filters]
|
||||
- when: on_success
|
||||
# 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_manual_in_mr:
|
||||
rules:
|
||||
- !reference [.job_rules, variable_filters]
|
||||
# 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:
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
when: manual
|
||||
allow_failure: true
|
||||
- when: on_success
|
||||
|
||||
.job_rules_integration:
|
||||
# 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:
|
||||
rules:
|
||||
- !reference [.job_rules, variable_filters]
|
||||
- if: $CI_COMMIT_REF_NAME =~ /^private/
|
||||
allow_failure: true
|
||||
- !reference [.rule_fragments, variable_filters]
|
||||
- when: on_success
|
||||
|
||||
@@ -32,7 +32,12 @@
|
||||
# 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.62.1'
|
||||
PLAYWRIGHT_VERSION: '1.63.0'
|
||||
|
||||
.variables_playwright_firefox:
|
||||
extends: .variables_playwright
|
||||
variables:
|
||||
SELENIUM_BROWSER: 'playwright_firefox'
|
||||
|
||||
.variables_proxy:
|
||||
variables:
|
||||
@@ -84,14 +89,13 @@
|
||||
alias: selenium-firefox
|
||||
|
||||
playwright:
|
||||
name: $CI_REGISTRY/docker/zammad-playwright:stable
|
||||
name: $CI_REGISTRY/docker/zammad-playwright:latest
|
||||
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. See
|
||||
# docker/zammad-playwright!2.
|
||||
# as the server is only reachable from this job.
|
||||
PLAYWRIGHT_UNSAFE: '1'
|
||||
|
||||
redis:
|
||||
|
||||
16
.gitlab/ci/__includes__/test_reports.yml
Normal file
16
.gitlab/ci/__includes__/test_reports.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
# 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
|
||||
@@ -29,6 +29,16 @@ 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.
|
||||
|
||||
@@ -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 =~ /^(private|cherry-pick-|revert-|renovate|dependabot)/ && $CI_PIPELINE_SOURCE == "push"
|
||||
- if: $CI_COMMIT_BRANCH =~ $CI_ZAMMAD_MR_ONLY_BRANCH_PATTERN && $CI_PIPELINE_SOURCE == "push"
|
||||
when: never
|
||||
- when: always
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
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"'
|
||||
before_script: []
|
||||
when: on_success
|
||||
- when: never
|
||||
script:
|
||||
- echo "⚠️ Disabling squash option for this MR..."
|
||||
- |
|
||||
@@ -20,6 +22,7 @@
|
||||
'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
|
||||
@@ -76,11 +79,12 @@
|
||||
# GITLAB_API_TOKEN - read_api token for the MR query (defaults to RENOVATE_TOKEN)
|
||||
'dependency digest':
|
||||
stage: automate
|
||||
cache: []
|
||||
rules:
|
||||
- if: $BROWSER_TEST_FILTER
|
||||
when: never
|
||||
- !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: $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'
|
||||
@@ -101,15 +105,12 @@
|
||||
image:
|
||||
name: ghcr.io/updatecli/updatecli:latest
|
||||
entrypoint: ['']
|
||||
cache: []
|
||||
rules:
|
||||
- if: $BROWSER_TEST_FILTER
|
||||
when: never
|
||||
- if: $JOB_FILTER && $JOB_FILTER != $CI_JOB_NAME
|
||||
when: never
|
||||
- !reference [.rule_fragments, main_repository_only]
|
||||
- !reference [.rule_fragments, variable_filters_without_force]
|
||||
- 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: []
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
.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
|
||||
@@ -19,8 +27,11 @@
|
||||
.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]
|
||||
@@ -34,13 +45,12 @@
|
||||
- !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
|
||||
|
||||
.template_browser-core_capybara_playwright_firefox:
|
||||
extends:
|
||||
- .template_browser-core_capybara
|
||||
- .variables_playwright_firefox
|
||||
services:
|
||||
- !reference [.services, postgresql]
|
||||
- !reference [.services, playwright]
|
||||
- !reference [.services, redis]
|
||||
|
||||
@@ -8,11 +8,25 @@ capybara:firefox:
|
||||
- .template_browser-core_capybara_firefox
|
||||
parallel: 16
|
||||
|
||||
# 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.
|
||||
# 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.
|
||||
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
|
||||
rules:
|
||||
- !reference [.rule_fragments, variable_filters]
|
||||
- !reference [.rule_fragments, then_manual]
|
||||
parallel: 16
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
.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
|
||||
@@ -19,6 +20,10 @@
|
||||
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]
|
||||
@@ -34,16 +39,6 @@
|
||||
- !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:
|
||||
@@ -52,7 +47,6 @@ 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
|
||||
@@ -60,4 +54,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
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
.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:
|
||||
- if: $BROWSER_TEST_FILTER
|
||||
when: always
|
||||
- when: never
|
||||
- !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
|
||||
variables:
|
||||
BROWSER_TEST_FILTER_LOOP_COUNT: 50
|
||||
script:
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
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]
|
||||
@@ -18,8 +23,13 @@ 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]
|
||||
@@ -32,7 +42,12 @@ capybara:integration:saml:
|
||||
.template_capybara_integration_otrs:
|
||||
extends:
|
||||
- .template_browser-core_capybara_chrome
|
||||
- .job_rules_manual_in_mr
|
||||
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]
|
||||
variables:
|
||||
IMPORT_OTRS_ENDPOINT: "http://zammad-ci-otrsimport-app/otrs/public.pl?Action=ZammadMigrator"
|
||||
services:
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
'write database dumps':
|
||||
stage: lint
|
||||
extends:
|
||||
- .template_ruby
|
||||
- .job_rules_default
|
||||
timeout: 6 minutes
|
||||
needs: !reference [.needs, ruby]
|
||||
services:
|
||||
- !reference [.services, postgresql]
|
||||
variables:
|
||||
|
||||
@@ -3,16 +3,18 @@
|
||||
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 [.scripts, source_rvm]
|
||||
- !reference [.scripts, bundle_install]
|
||||
- !reference [.scripts, configure_environment]
|
||||
- !reference [.template_ruby, before_script]
|
||||
- !reference [.scripts, zammad_db_init]
|
||||
variables:
|
||||
ZAMMAD_SAFE_MODE: 1
|
||||
@@ -21,16 +23,16 @@
|
||||
'lint: shellcheck':
|
||||
extends:
|
||||
- .template_lint
|
||||
timeout: 2 minutes
|
||||
image: koalaman/shellcheck-alpine:stable
|
||||
cache: []
|
||||
before_script: []
|
||||
script:
|
||||
- shellcheck -S warning $(find . -name "*.sh" -o -name "functions" | egrep -v "/vendor|node_modules/")
|
||||
- find . \( -name vendor -o -name node_modules \) -prune -o \( -name '*.sh' -o -name functions \) -print0 | xargs -0 shellcheck -S warning
|
||||
after_script: []
|
||||
|
||||
'lint: i18n & rails':
|
||||
extends:
|
||||
- .template_lint_rails
|
||||
timeout: 8 minutes
|
||||
cache: !reference [.cache, read_only_full]
|
||||
script:
|
||||
- !reference [.scripts, pnpm_install]
|
||||
@@ -47,6 +49,8 @@
|
||||
'lint: ruby & security':
|
||||
extends:
|
||||
- .template_lint
|
||||
- .template_ruby
|
||||
timeout: 10 minutes
|
||||
before_script:
|
||||
- !reference [.scripts, source_rvm]
|
||||
- !reference [.scripts, bundle_install]
|
||||
@@ -63,8 +67,10 @@
|
||||
- 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…"
|
||||
- bundle exec brakeman -o /dev/stdout -o tmp/brakeman-report.html
|
||||
- bin/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"
|
||||
@@ -76,6 +82,7 @@
|
||||
'lint: markdown, coffee & css':
|
||||
extends:
|
||||
- .template_lint
|
||||
timeout: 5 minutes
|
||||
cache: !reference [.cache, read_only_nodejs]
|
||||
before_script:
|
||||
- !reference [.scripts, pnpm_install]
|
||||
@@ -90,6 +97,7 @@
|
||||
'lint: js':
|
||||
extends:
|
||||
- .template_lint
|
||||
timeout: 10 minutes
|
||||
cache:
|
||||
- !reference [.cache, read_only_nodejs]
|
||||
- !reference [.cache, read_write_eslint]
|
||||
@@ -116,22 +124,21 @@
|
||||
'lint: orphaned ruby gems':
|
||||
extends:
|
||||
- .template_lint_rails
|
||||
timeout: 2 minutes
|
||||
allow_failure: true
|
||||
rules:
|
||||
- !reference [.job_rules, variable_filters]
|
||||
- when: manual
|
||||
- !reference [.rule_fragments, variable_filters]
|
||||
- !reference [.rule_fragments, then_manual]
|
||||
script:
|
||||
- bundle exec rake zammad:ci:bundle:orphaned 6
|
||||
|
||||
'update CI variables':
|
||||
extends:
|
||||
- .template_lint_rails
|
||||
timeout: 4 minutes
|
||||
rules:
|
||||
- !reference [.job_rules, variable_filters]
|
||||
- if: $CI_PIPELINE_SOURCE != "schedule"
|
||||
when: manual
|
||||
allow_failure: true
|
||||
- when: on_success
|
||||
- !reference [.rule_fragments, variable_filters]
|
||||
- !reference [.rule_fragments, then_manual_unless_scheduled]
|
||||
script:
|
||||
- bundle exec rake zammad:ci:update_ci_variables
|
||||
|
||||
@@ -140,13 +147,21 @@
|
||||
image:
|
||||
name: 'zricethezav/gitleaks:latest'
|
||||
entrypoint: ['']
|
||||
cache: []
|
||||
before_script: []
|
||||
timeout: 2 minutes
|
||||
variables:
|
||||
# Go uses all CPUs by default.
|
||||
GOMAXPROCS: 1
|
||||
script:
|
||||
# Since we clone with GIT_DEPTH=1, the commit has the entire codebase as a diff.
|
||||
# Otherwise, we'd need to use --no-git to scan the entire codebase, but that is slower
|
||||
# as it also traverses directories not scanned by git.
|
||||
- gitleaks detect --report-path secret-detection-report.json --verbose
|
||||
# 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.
|
||||
- git fetch --quiet --deepen=99 origin "$CI_COMMIT_SHA"
|
||||
# Scan only the commits the pipeline adds. Without a base commit in the checkout
|
||||
# (scheduled pipelines, new branches, force pushes), scan only the current commit.
|
||||
- BASE="${CI_MERGE_REQUEST_DIFF_BASE_SHA:-$CI_COMMIT_BEFORE_SHA}"
|
||||
- git merge-base --is-ancestor "$BASE" HEAD 2>/dev/null || BASE='HEAD~1'
|
||||
- set -o pipefail; gitleaks git --log-opts="${BASE}..HEAD" --report-path secret-detection-report.json --verbose --no-color 2>&1 | tee gitleaks.log
|
||||
# gitleaks only logs errors of the git commands it runs and still exits successfully.
|
||||
- if grep -qE '(^| )ERR ' gitleaks.log; then echo 'gitleaks reported errors, failing.'; exit 1; fi
|
||||
after_script: []
|
||||
# # GitLab can show this in a security widget, but that seems to be useless at this point (offers empty file for download).
|
||||
# artifacts:
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# 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]
|
||||
@@ -8,20 +10,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 =~ /^(private|cherry-pick-|revert-|renovate|dependabot)/
|
||||
- if: $CI_COMMIT_REF_NAME =~ $CI_ZAMMAD_MR_ONLY_BRANCH_PATTERN
|
||||
when: never
|
||||
- when: always
|
||||
before_script:
|
||||
@@ -39,8 +41,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
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
# Build/update docker image and publish it to the GitLab container registry.
|
||||
- |
|
||||
echo "Build docker image…"
|
||||
docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
|
||||
echo "$CI_JOB_TOKEN" | docker login -u gitlab-ci-token --password-stdin $CI_REGISTRY
|
||||
echo -e "\\e[0Ksection_start:`date +%s`:docker_build[collapsed=true]\\r\\e[0Kdocker build"
|
||||
if [ ! "${SKIP_DOCKER_BUILD}" ]
|
||||
then
|
||||
@@ -231,7 +231,6 @@
|
||||
tags:
|
||||
- review-apps
|
||||
stage: pre
|
||||
cache: []
|
||||
variables:
|
||||
SERVER_NAME: review-apps.dc.zammad.com
|
||||
APP_SLUG: $CI_COMMIT_REF_SLUG
|
||||
@@ -242,7 +241,10 @@
|
||||
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
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
.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]
|
||||
@@ -15,10 +20,15 @@ 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"
|
||||
@@ -30,11 +40,15 @@ api_client:php:
|
||||
- ln -s /usr/local/bin/composer.phar /usr/local/bin/composer
|
||||
- cd zammad-api-client-php
|
||||
- composer install
|
||||
- vendor/bin/phpunit
|
||||
- vendor/bin/phpunit --no-coverage
|
||||
|
||||
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
|
||||
@@ -43,6 +57,7 @@ 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
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
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:
|
||||
@@ -28,7 +39,14 @@ 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]
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
# 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: []
|
||||
needs:
|
||||
- !reference [.needs, lint]
|
||||
timeout: 15 minutes
|
||||
variables:
|
||||
DOCKER_HOST: unix:///var/run/docker.sock
|
||||
DEVCONTAINERS_CLI_VERSION: '0.87.0'
|
||||
cache: []
|
||||
DEVCONTAINERS_CLI_VERSION: '0.89.0'
|
||||
before_script:
|
||||
- apk add --no-cache docker-cli docker-cli-buildx docker-compose
|
||||
after_script: []
|
||||
@@ -23,8 +24,5 @@
|
||||
--config .devcontainer/$VARIANT/devcontainer.json
|
||||
--frozen-lockfile
|
||||
rules:
|
||||
- !reference [.job_rules, variable_filters]
|
||||
- if: $CI_PIPELINE_SOURCE == "schedule"
|
||||
when: on_success
|
||||
- when: manual
|
||||
allow_failure: true
|
||||
- !reference [.rule_fragments, variable_filters]
|
||||
- !reference [.rule_fragments, then_manual_unless_scheduled]
|
||||
|
||||
@@ -1,48 +1,73 @@
|
||||
docker:
|
||||
.template_docker:
|
||||
image: docker
|
||||
stage: test
|
||||
extends:
|
||||
- .no_artifact_dependencies
|
||||
needs:
|
||||
- !reference [.needs, lint]
|
||||
timeout: 30 minutes
|
||||
interruptible: false
|
||||
rules:
|
||||
- !reference [.job_rules, variable_filters]
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_COMMIT_REF_NAME =~ /^(private|cherry-pick-|revert-|renovate|dependabot)/
|
||||
- !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
|
||||
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: "$CI_REGISTRY/zammad/zammad"
|
||||
ZAMMAD_IMAGE_REPO: "zammad"
|
||||
ZAMMAD_IMAGE: "${ZAMMAD_IMAGE_REPO}:${ZAMMAD_IMAGE_TAG}"
|
||||
before_script:
|
||||
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
|
||||
- docker build --build-arg COMMIT_SHA=${CI_COMMIT_SHORT_SHA} --pull -t $ZAMMAD_IMAGE .
|
||||
- docker push $ZAMMAD_IMAGE
|
||||
script: |
|
||||
git clone --depth=1 https://github.com/zammad/zammad-docker-compose.git
|
||||
cd zammad-docker-compose
|
||||
|
||||
- echo "$CI_JOB_TOKEN" | docker login -u gitlab-ci-token --password-stdin $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}" > .env
|
||||
echo "VERSION=${ZAMMAD_IMAGE_TAG}" >> .env
|
||||
cat .env
|
||||
|
||||
- echo "IMAGE_REPO=${ZAMMAD_IMAGE_REPO}" > zammad-docker-compose/.env
|
||||
- echo "VERSION=${ZAMMAD_IMAGE_TAG}" >> zammad-docker-compose/.env
|
||||
- cat zammad-docker-compose/.env
|
||||
# Use dynamic stack name and no exposed port to avoid conflicts.
|
||||
cat - > docker-compose.override.yml <<COMPOSE_OVERRIDE_FILE
|
||||
---
|
||||
name: zammad-docker-ci-${ZAMMAD_IMAGE_TAG}
|
||||
services:
|
||||
zammad-nginx:
|
||||
ports: !reset []
|
||||
COMPOSE_OVERRIDE_FILE
|
||||
|
||||
.github/tests/setup/default.sh
|
||||
DISABLE_EXTERNAL_TESTS=1 .github/tests/default.sh
|
||||
|
||||
docker compose ps
|
||||
# Ensure we are running with the correct image
|
||||
docker compose ps | grep ${ZAMMAD_IMAGE}
|
||||
- |
|
||||
cat - > zammad-docker-compose/docker-compose.override.yml <<COMPOSE_OVERRIDE_FILE
|
||||
---
|
||||
name: zammad-docker-ci-${ZAMMAD_IMAGE_TAG}
|
||||
services:
|
||||
zammad-nginx:
|
||||
ports: !reset []
|
||||
COMPOSE_OVERRIDE_FILE
|
||||
after_script:
|
||||
- cd zammad-docker-compose
|
||||
- docker compose down --timeout 0 --volumes
|
||||
- docker image rm ${ZAMMAD_IMAGE}
|
||||
|
||||
docker base tests:
|
||||
extends: .template_docker
|
||||
script:
|
||||
- docker build --build-arg COMMIT_SHA=${CI_COMMIT_SHORT_SHA} --pull -t $ZAMMAD_IMAGE .
|
||||
- cd zammad-docker-compose
|
||||
- .github/tests/setup/default.sh
|
||||
- DISABLE_EXTERNAL_TESTS=1 .github/tests/default.sh
|
||||
- docker compose ps
|
||||
# Ensure we are running with the correct image
|
||||
- docker compose ps | grep ${ZAMMAD_IMAGE}
|
||||
|
||||
docker addon management:
|
||||
extends: .template_docker
|
||||
script:
|
||||
- mkdir -p packages/install packages/uninstall
|
||||
# part 1: test package installation
|
||||
- cp .gitlab/docker/*.zpm packages/install/
|
||||
- docker build --build-arg COMMIT_SHA=${CI_COMMIT_SHORT_SHA} --pull -t $ZAMMAD_IMAGE .
|
||||
- cd zammad-docker-compose
|
||||
- echo "ELASTICSEARCH_ENABLED=false" >> .env
|
||||
- docker compose run --rm zammad-init
|
||||
- |
|
||||
docker compose run --rm zammad-railsserver bundle exec rails r "Setting.where(name: ['zammad_test_a_addon_up', 'zammad_test_b_addon_up']).count == 2 || raise('Setting not found')"
|
||||
- cd ..
|
||||
# part 2: test package uninstallation
|
||||
- mv packages/install/*.zpm packages/uninstall
|
||||
- docker build --build-arg COMMIT_SHA=${CI_COMMIT_SHORT_SHA} --pull -t $ZAMMAD_IMAGE .
|
||||
- cd zammad-docker-compose
|
||||
- docker compose run --rm zammad-init
|
||||
- |
|
||||
docker compose run --rm zammad-railsserver bundle exec rails r "Setting.where(name: ['zammad_test_a_addon_down', 'zammad_test_b_addon_down']).count == 2 || raise('Setting not found')"
|
||||
|
||||
@@ -2,13 +2,20 @@
|
||||
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.
|
||||
- pnpm test --max-workers=1 --shard $CI_NODE_INDEX/$CI_NODE_TOTAL
|
||||
# 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
|
||||
# Stop here, except on the first shard.
|
||||
- "[[ $CI_NODE_INDEX = 1 ]] || exit 0"
|
||||
- echo "Also test eslint-plugin-zammad…"
|
||||
@@ -25,6 +32,11 @@ 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]
|
||||
|
||||
@@ -3,10 +3,24 @@ include:
|
||||
|
||||
.template_integration:
|
||||
extends:
|
||||
- .job_rules_integration
|
||||
- .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
|
||||
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
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
.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
|
||||
@@ -19,8 +27,8 @@ rspec:es:
|
||||
matrix:
|
||||
- ELASTICSEARCH_TAG: [stable, latest]
|
||||
rules:
|
||||
- !reference [.job_rules, variable_filters]
|
||||
- !reference [.rule_fragments, variable_filters]
|
||||
# The lowest supported version is covered in every pipeline.
|
||||
- if: $ELASTICSEARCH_TAG == "stable"
|
||||
- if: $CI_PIPELINE_SOURCE == "schedule"
|
||||
- when: manual
|
||||
allow_failure: true
|
||||
when: on_success
|
||||
- !reference [.rule_fragments, then_manual_unless_scheduled]
|
||||
|
||||
@@ -20,9 +20,8 @@
|
||||
extends:
|
||||
- .template_integration_otrs
|
||||
rules:
|
||||
- !reference [.job_rules, variable_filters]
|
||||
- when: manual
|
||||
allow_failure: true
|
||||
- !reference [.rule_fragments, variable_filters]
|
||||
- !reference [.rule_fragments, then_manual]
|
||||
|
||||
rspec:integration:otrs:6:
|
||||
extends:
|
||||
|
||||
@@ -5,26 +5,31 @@ 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 --unshallow
|
||||
- git checkout stable-6.0
|
||||
- git fetch --depth=1 origin stable-6.0
|
||||
- git checkout FETCH_HEAD
|
||||
- !reference [.scripts, source_rvm]
|
||||
- rvm use 3.1.3
|
||||
- rvm use $(cat .ruby-version)
|
||||
- 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 3.4.9
|
||||
- rvm use $(cat .ruby-version)
|
||||
- !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
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
.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:
|
||||
@@ -20,4 +28,4 @@ packager.io:
|
||||
extends: [.packager.io]
|
||||
parallel:
|
||||
matrix:
|
||||
- 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]
|
||||
- DISTRO: [debian-12, debian-13, ubuntu-22.04, ubuntu-24.04, ubuntu-26.04, rhel-9, rhel-10, sles-15, sles-16.0]
|
||||
|
||||
@@ -1,29 +1,35 @@
|
||||
performance:
|
||||
stage: test
|
||||
needs:
|
||||
- !reference [.needs, lint]
|
||||
timeout: 45 minutes
|
||||
rules:
|
||||
- !reference [.job_rules, variable_filters]
|
||||
- !reference [.rule_fragments, 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 --unshallow >/dev/null 2>&1
|
||||
- git checkout stable-6.0
|
||||
- git fetch --depth=1 origin stable-6.0
|
||||
- git checkout FETCH_HEAD
|
||||
- !reference [.scripts, source_rvm]
|
||||
- rvm use 3.1.3
|
||||
- rvm use $(cat .ruby-version)
|
||||
- bundle config set without mysql
|
||||
- !reference [.scripts, bundle_install]
|
||||
- !reference [.scripts, configure_environment]
|
||||
@@ -35,7 +41,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 3.4.9
|
||||
- rvm use $(cat .ruby-version)
|
||||
- !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
|
||||
|
||||
@@ -4,7 +4,14 @@ 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
|
||||
@@ -19,13 +26,28 @@ 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 [.job_rules, variable_filters]
|
||||
- !reference [.rule_fragments, variable_filters]
|
||||
- if: $CI_PIPELINE_SOURCE == "schedule"
|
||||
when: on_success
|
||||
allow_failure: true
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
changes:
|
||||
@@ -67,9 +89,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
|
||||
- when: manual
|
||||
allow_failure: true
|
||||
- !reference [.rule_fragments, then_manual]
|
||||
services:
|
||||
- !reference [.services, postgresql]
|
||||
- !reference [.services, imap]
|
||||
|
||||
@@ -2,14 +2,21 @@
|
||||
addon:subscription:
|
||||
stage: test
|
||||
extends:
|
||||
- .job_rules_default
|
||||
- .template_ruby
|
||||
- .artifacts_with_test_report
|
||||
needs:
|
||||
- !reference [.needs, ruby]
|
||||
- !reference [.needs, database]
|
||||
- !reference [.needs, lint]
|
||||
timeout: 15 minutes
|
||||
rules:
|
||||
- !reference [.job_rules, variable_filters]
|
||||
- !reference [.rule_fragments, main_repository_only]
|
||||
- !reference [.rule_fragments, 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|develop)/
|
||||
- if: $CI_COMMIT_REF_NAME =~ /^private/
|
||||
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.
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
.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:
|
||||
@@ -11,9 +18,10 @@ supabase:translation_stats:
|
||||
extends:
|
||||
- .template_supabase
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "schedule" && $CI_COMMIT_REF_NAME =~ /^(develop|stable)$/
|
||||
when: always
|
||||
- when: never
|
||||
- !reference [.rule_fragments, main_repository_only]
|
||||
- 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
|
||||
|
||||
@@ -21,6 +29,7 @@ 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
|
||||
|
||||
39
.gitlab/docker/ZammadTestA-1.0.0.yaml
Normal file
39
.gitlab/docker/ZammadTestA-1.0.0.yaml
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
name: ZammadTestA
|
||||
version: 1.0.0
|
||||
vendor: Zammad GmbH
|
||||
license: GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
url: http://zammad.com/
|
||||
change_log: []
|
||||
dependencies:
|
||||
ZammadTestB: ">= 1.0.0"
|
||||
description:
|
||||
- language: en
|
||||
text: Test package for CI
|
||||
- language: de
|
||||
text: Testpaket für die CI
|
||||
buildhost: zammad42
|
||||
builddate: 2026-08-19 15:29:46 UTC
|
||||
files:
|
||||
- location: db/addon/zammad_test_a/20251112113130_zammad_test_a_settings.rb
|
||||
permission: 644
|
||||
content: |
|
||||
class ZammadTestASettings < ActiveRecord::Migration[8.0]
|
||||
def self.up
|
||||
Setting.create_if_not_exists(
|
||||
title: 'Zammad Test A Addon Up',
|
||||
name: 'zammad_test_a_addon_up',
|
||||
area: 'Core',
|
||||
description: 'Verify that the addon was installed.',
|
||||
)
|
||||
end
|
||||
|
||||
def self.down
|
||||
Setting.create_if_not_exists(
|
||||
title: 'Zammad Test A Addon Down',
|
||||
name: 'zammad_test_a_addon_down',
|
||||
area: 'Core',
|
||||
description: 'Verify that the addon was removed.',
|
||||
)
|
||||
end
|
||||
end
|
||||
31
.gitlab/docker/ZammadTestA-1.0.0.zpm
Normal file
31
.gitlab/docker/ZammadTestA-1.0.0.zpm
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "ZammadTestA",
|
||||
"version": "1.0.0",
|
||||
"vendor": "Zammad GmbH",
|
||||
"license": "GNU AFFERO GENERAL PUBLIC LICENSE",
|
||||
"url": "http://zammad.com/",
|
||||
"change_log": [],
|
||||
"dependencies": {
|
||||
"ZammadTestB": ">= 1.0.0"
|
||||
},
|
||||
"description": [
|
||||
{
|
||||
"language": "en",
|
||||
"text": "Test package for CI"
|
||||
},
|
||||
{
|
||||
"language": "de",
|
||||
"text": "Testpaket für die CI"
|
||||
}
|
||||
],
|
||||
"buildhost": "zammad42",
|
||||
"builddate": "2026-08-19 15:29:46 UTC",
|
||||
"files": [
|
||||
{
|
||||
"location": "db/addon/zammad_test_a/20251112113130_zammad_test_a_settings.rb",
|
||||
"permission": 644,
|
||||
"content": "Y2xhc3MgWmFtbWFkVGVzdEFTZXR0aW5ncyA8IEFjdGl2ZVJlY29yZDo6TWlncmF0aW9uWzguMF0KICBkZWYgc2VsZi51cAogICAgU2V0dGluZy5jcmVhdGVfaWZfbm90X2V4aXN0cygKICAgICAgdGl0bGU6ICAgICAgICdaYW1tYWQgVGVzdCBBIEFkZG9uIFVwJywKICAgICAgbmFtZTogICAgICAgICd6YW1tYWRfdGVzdF9hX2FkZG9uX3VwJywKICAgICAgYXJlYTogICAgICAgICdDb3JlJywKICAgICAgZGVzY3JpcHRpb246ICdWZXJpZnkgdGhhdCB0aGUgYWRkb24gd2FzIGluc3RhbGxlZC4nLAogICAgKQogIGVuZAoKICBkZWYgc2VsZi5kb3duCiAgICBTZXR0aW5nLmNyZWF0ZV9pZl9ub3RfZXhpc3RzKAogICAgICB0aXRsZTogICAgICAgJ1phbW1hZCBUZXN0IEEgQWRkb24gRG93bicsCiAgICAgIG5hbWU6ICAgICAgICAnemFtbWFkX3Rlc3RfYV9hZGRvbl9kb3duJywKICAgICAgYXJlYTogICAgICAgICdDb3JlJywKICAgICAgZGVzY3JpcHRpb246ICdWZXJpZnkgdGhhdCB0aGUgYWRkb24gd2FzIHJlbW92ZWQuJywKICAgICkKICBlbmQKZW5kCg==",
|
||||
"encode": "base64"
|
||||
}
|
||||
]
|
||||
}
|
||||
37
.gitlab/docker/ZammadTestB-1.0.0.yaml
Normal file
37
.gitlab/docker/ZammadTestB-1.0.0.yaml
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
name: ZammadTestB
|
||||
version: 1.0.0
|
||||
vendor: Zammad GmbH
|
||||
license: GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
url: http://zammad.com/
|
||||
change_log: []
|
||||
description:
|
||||
- language: en
|
||||
text: Test package for CI
|
||||
- language: de
|
||||
text: Testpaket für die CI
|
||||
buildhost: zammad42
|
||||
builddate: 2026-08-19 15:29:46 UTC
|
||||
files:
|
||||
- location: db/addon/zammad_test_b/20251112113130_zammad_test_b_settings.rb
|
||||
permission: 644
|
||||
content: |
|
||||
class ZammadTestBSettings < ActiveRecord::Migration[8.0]
|
||||
def self.up
|
||||
Setting.create_if_not_exists(
|
||||
title: 'Zammad Test B Addon Up',
|
||||
name: 'zammad_test_b_addon_up',
|
||||
area: 'Core',
|
||||
description: 'Verify that the addon was installed.',
|
||||
)
|
||||
end
|
||||
|
||||
def self.down
|
||||
Setting.create_if_not_exists(
|
||||
title: 'Zammad Test B Addon Down',
|
||||
name: 'zammad_test_b_addon_down',
|
||||
area: 'Core',
|
||||
description: 'Verify that the addon was removed.',
|
||||
)
|
||||
end
|
||||
end
|
||||
28
.gitlab/docker/ZammadTestB-1.0.0.zpm
Normal file
28
.gitlab/docker/ZammadTestB-1.0.0.zpm
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "ZammadTestB",
|
||||
"version": "1.0.0",
|
||||
"vendor": "Zammad GmbH",
|
||||
"license": "GNU AFFERO GENERAL PUBLIC LICENSE",
|
||||
"url": "http://zammad.com/",
|
||||
"change_log": [],
|
||||
"description": [
|
||||
{
|
||||
"language": "en",
|
||||
"text": "Test package for CI"
|
||||
},
|
||||
{
|
||||
"language": "de",
|
||||
"text": "Testpaket für die CI"
|
||||
}
|
||||
],
|
||||
"buildhost": "zammad42",
|
||||
"builddate": "2026-08-19 15:29:46 UTC",
|
||||
"files": [
|
||||
{
|
||||
"location": "db/addon/zammad_test_b/20251112113130_zammad_test_b_settings.rb",
|
||||
"permission": 644,
|
||||
"content": "Y2xhc3MgWmFtbWFkVGVzdEJTZXR0aW5ncyA8IEFjdGl2ZVJlY29yZDo6TWlncmF0aW9uWzguMF0KICBkZWYgc2VsZi51cAogICAgU2V0dGluZy5jcmVhdGVfaWZfbm90X2V4aXN0cygKICAgICAgdGl0bGU6ICAgICAgICdaYW1tYWQgVGVzdCBCIEFkZG9uIFVwJywKICAgICAgbmFtZTogICAgICAgICd6YW1tYWRfdGVzdF9iX2FkZG9uX3VwJywKICAgICAgYXJlYTogICAgICAgICdDb3JlJywKICAgICAgZGVzY3JpcHRpb246ICdWZXJpZnkgdGhhdCB0aGUgYWRkb24gd2FzIGluc3RhbGxlZC4nLAogICAgKQogIGVuZAoKICBkZWYgc2VsZi5kb3duCiAgICBTZXR0aW5nLmNyZWF0ZV9pZl9ub3RfZXhpc3RzKAogICAgICB0aXRsZTogICAgICAgJ1phbW1hZCBUZXN0IEIgQWRkb24gRG93bicsCiAgICAgIG5hbWU6ICAgICAgICAnemFtbWFkX3Rlc3RfYl9hZGRvbl9kb3duJywKICAgICAgYXJlYTogICAgICAgICdDb3JlJywKICAgICAgZGVzY3JpcHRpb246ICdWZXJpZnkgdGhhdCB0aGUgYWRkb24gd2FzIHJlbW92ZWQuJywKICAgICkKICBlbmQKZW5kCg==",
|
||||
"encode": "base64"
|
||||
}
|
||||
]
|
||||
}
|
||||
41
.gitlab/docker/yaml_to_zpm.rb
Executable file
41
.gitlab/docker/yaml_to_zpm.rb
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env ruby
|
||||
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
# Converts all package definitions in YAML form found next to this script to the
|
||||
# JSON based .zpm format, written to a file of the same base name. The content of
|
||||
# all entries in "files" is transcoded to base64.
|
||||
#
|
||||
# ruby yaml_to_zpm.rb
|
||||
#
|
||||
# ZammadTestA-1.0.0.yaml -> ZammadTestA-1.0.0.zpm
|
||||
|
||||
require 'base64'
|
||||
require 'json'
|
||||
require 'yaml'
|
||||
|
||||
def convert(source, target)
|
||||
package = YAML.safe_load_file(source, aliases: true)
|
||||
|
||||
raise "Expected a YAML mapping as package definition in #{source}." if !package.is_a?(Hash)
|
||||
|
||||
Array(package['files']).each do |file|
|
||||
next if !file.is_a?(Hash) || !file.key?('content')
|
||||
|
||||
file['encode'] = 'base64'
|
||||
file['content'] = Base64.strict_encode64(file['content'].to_s)
|
||||
end
|
||||
|
||||
File.write(target, "#{JSON.pretty_generate(package)}\n")
|
||||
end
|
||||
|
||||
sources = Dir.glob('*.{yaml,yml}', base: __dir__).sort
|
||||
|
||||
raise 'No YAML package definitions found.' if sources.empty?
|
||||
|
||||
sources.each do |source|
|
||||
target = "#{File.basename(source, '.*')}.zpm"
|
||||
|
||||
convert(File.join(__dir__, source), File.join(__dir__, target))
|
||||
|
||||
puts "#{source} -> #{target}"
|
||||
end
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/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
|
||||
@@ -6,6 +6,9 @@ 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
|
||||
|
||||
@@ -2,6 +2,21 @@
|
||||
|
||||
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"
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
{
|
||||
"enabledManagers": ["bundler", "npm", "github-actions", "devcontainer", "custom.regex"],
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"ignorePaths": ["**/node_modules/**"],
|
||||
"enabledManagers": [
|
||||
"bundler",
|
||||
"npm",
|
||||
"github-actions",
|
||||
"devcontainer",
|
||||
"dockerfile",
|
||||
"docker-compose",
|
||||
"gitlabci",
|
||||
"custom.regex"
|
||||
],
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
@@ -10,6 +21,15 @@
|
||||
"depNameTemplate": "@devcontainers/cli",
|
||||
"datasourceTemplate": "npm"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": [
|
||||
"/^\\.gitlab/ci/.*\\.yml$/"
|
||||
],
|
||||
"matchStrings": ["PLAYWRIGHT_VERSION: '(?<currentValue>[^']+)'"],
|
||||
"depNameTemplate": "playwright",
|
||||
"datasourceTemplate": "npm"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": [
|
||||
@@ -27,6 +47,11 @@
|
||||
"commitMessageExtra": "(Ruby)"
|
||||
}
|
||||
},
|
||||
"gitlabci": {
|
||||
"managerFilePatterns": [
|
||||
"/^\\.gitlab/ci/.*\\.yml$/"
|
||||
]
|
||||
},
|
||||
"devcontainer": {
|
||||
"managerFilePatterns": [
|
||||
"/^\\.devcontainer/[^/]+/devcontainer\\.json$/"
|
||||
@@ -41,6 +66,11 @@
|
||||
}
|
||||
},
|
||||
"minimumReleaseAge": "3 days",
|
||||
"osvVulnerabilityAlerts": true,
|
||||
"vulnerabilityAlerts": {
|
||||
"minimumReleaseAge": null,
|
||||
"addLabels": ["security"]
|
||||
},
|
||||
"assignees": ["fl"],
|
||||
"packageRules": [
|
||||
{
|
||||
@@ -65,8 +95,35 @@
|
||||
},
|
||||
{
|
||||
"matchCategories": ["js"],
|
||||
"matchFileNames": [".cypress/package.json"],
|
||||
"labels": ["cypress"]
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"matchCategories": ["js"],
|
||||
@@ -74,6 +131,12 @@
|
||||
"groupSlug": "eslint",
|
||||
"matchPackageNames": ["/eslint/"]
|
||||
},
|
||||
{
|
||||
"matchCategories": ["js"],
|
||||
"groupName": "Oxc packages",
|
||||
"groupSlug": "oxc",
|
||||
"matchPackageNames": ["oxlint", "oxfmt", "eslint-plugin-oxlint"]
|
||||
},
|
||||
{
|
||||
"matchCategories": ["js"],
|
||||
"groupName": "Stylelint packages",
|
||||
@@ -118,7 +181,8 @@
|
||||
"/^@vue/",
|
||||
"/^vue/",
|
||||
"!@vue/eslint-config-typescript",
|
||||
"!@vuepic/vue-datepicker"
|
||||
"!@vuepic/vue-datepicker",
|
||||
"!/^@vueuse/"
|
||||
],
|
||||
"groupName": "Vue packages",
|
||||
"groupSlug": "vue"
|
||||
|
||||
@@ -2,24 +2,7 @@
|
||||
"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",
|
||||
|
||||
14
.pkgr.yml
14
.pkgr.yml
@@ -35,20 +35,6 @@ 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
|
||||
|
||||
@@ -2,6 +2,32 @@
|
||||
|
||||
## 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
|
||||
@@ -15,32 +41,6 @@ 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,8 +71,191 @@ 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 & 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
|
||||
|
||||
**Who is affected?** Integrations that redeem a signup verification token through
|
||||
`POST /api/v1/users/email_verify` or the `userSignupVerify` GraphQL mutation and rely on the session
|
||||
that the redemption used to establish, and admins who send a verification or password reset link to a
|
||||
deactivated account.
|
||||
|
||||
Redeeming the token established a fully authenticated session for the account, without the account's
|
||||
password and without the second factor configured for it. Both endpoints now only mark the email
|
||||
address as verified and establish no session. The REST endpoint still answers
|
||||
`200 {"message":"ok","user_email":"…"}`, but the response no longer carries a session cookie. The
|
||||
mutation's payload field `session` was replaced by a boolean `success`, so a client selecting
|
||||
`session { id }` receives a GraphQL error.
|
||||
|
||||
A non-persistent token — `Signup`, `PasswordReset`, `AdminAuth`, `KnowledgeBasePreview` — is now
|
||||
rejected when it belongs to an inactive user. Previously only persistent tokens were checked this
|
||||
way. The mail carrying such a link is still sent, but the link itself no longer works while the
|
||||
account is deactivated.
|
||||
|
||||
⚠️ Sign in through `POST /api/v1/signin` or the `login` mutation after redeeming a verification
|
||||
token, and pass the configured second factor there. Change GraphQL clients to select `success`
|
||||
instead of `session`. Activate a deactivated account before sending it a verification or password
|
||||
reset link.
|
||||
|
||||
**Related advisory:** [GHSA-f3qr-94c2-7mx2](https://github.com/zammad/zammad/security/advisories/GHSA-f3qr-94c2-7mx2)
|
||||
|
||||
### The getting started endpoints require `admin.wizard` once the system is set up
|
||||
|
||||
**Who is affected?** Integrations that read `GET /api/v1/getting_started` or
|
||||
`GET /api/v1/getting_started/auto_wizard` while authenticated as a user without the `admin.wizard`
|
||||
permission, and any integration that reads them while a migration is running.
|
||||
|
||||
On a system that is set up, both endpoints returned the list of active groups and of active email
|
||||
addresses, the branding config and the available email channel drivers to every authenticated user,
|
||||
customers included. That data is only of use to a user who may run the setup wizard, and it is
|
||||
restricted on its own endpoints: `GET /api/v1/email_addresses` answers a customer with `403`, and
|
||||
`GET /api/v1/groups/:id` field-scopes its response. Both endpoints now answer a user without
|
||||
`admin.wizard` with `403 Forbidden` instead.
|
||||
|
||||
Before the setup, and while a migration is running, the endpoints stay reachable without
|
||||
authentication: a migration is started anonymously from the installer, and its progress screens have
|
||||
no user to authenticate as. While a migration is running the response is now reduced to the setup and
|
||||
migration state — `setup_done`, `import_mode`, `import_backend` and `system_online_service` — which
|
||||
is all those screens read; the group and email address list, the branding config and the email
|
||||
channel drivers are no longer part of it, for any caller. A migration only clears its import mode
|
||||
when it succeeds, so a failed one would otherwise keep that data readable without authentication
|
||||
indefinitely. Outside a migration the response is unchanged for users with `admin.wizard`.
|
||||
|
||||
⚠️ Switch integrations that read the group or email address list from these endpoints to an account
|
||||
with the `admin.wizard` permission — an API token needs that permission in its scope as well — or to
|
||||
`GET /api/v1/groups` respectively `GET /api/v1/email_addresses`.
|
||||
|
||||
**Related advisory:** [GHSA-x3fm-p95w-xj7v](https://github.com/zammad/zammad/security/advisories/GHSA-x3fm-p95w-xj7v)
|
||||
|
||||
### The legacy WebSocket login is authenticated from the session cookie and `signshow` no longer returns `session_id`
|
||||
|
||||
**Who is affected?** Developers of custom clients that connect to the legacy WebSocket server and
|
||||
authenticate with a `session_id` in the `login` event, integrations that read `session_id` from the
|
||||
`GET /api/v1/signshow` response, and admins whose `http_type` and `fqdn` settings do not match the URL
|
||||
users open Zammad with.
|
||||
|
||||
The `GET /api/v1/signshow` response contained the ID of the current session for logged-in users, and the
|
||||
legacy frontend sent that ID in the `login` event of the WebSocket connection to authenticate it. Exposing
|
||||
the session ID to page scripts was unnecessary and made it easier to steal a session. The `session_id`
|
||||
key was removed from the response, and the WebSocket server now ignores any `session_id` in the `login`
|
||||
payload. Instead it resolves the user from the session cookie sent with the WebSocket handshake, in the
|
||||
same way ActionCable already does for the new frontend, and only if the handshake `Origin` header matches
|
||||
`<http_type>://<fqdn>` or a localhost origin. A `login` event without a matching cookie and origin creates
|
||||
an unauthenticated WebSocket session that receives no user-specific data.
|
||||
|
||||
⚠️ Send the session cookie with the WebSocket handshake instead of a `session_id` in the `login` payload,
|
||||
and make sure the `http_type` and `fqdn` settings match the origin users open Zammad with.
|
||||
|
||||
**Related advisory:** [GHSA-23hj-h8w6-rgm3](https://github.com/zammad/zammad/security/advisories/GHSA-23hj-h8w6-rgm3)
|
||||
|
||||
### Zammad's reverse proxy configuration is authoritative for the request scheme
|
||||
|
||||
**Who is affected?** Admins running the Docker images or a newly installed nginx behind a proxy
|
||||
that reports the request scheme via the `Forwarded` or `X-Forwarded-Ssl` header, and admins of a
|
||||
newly installed Apache behind any TLS-terminating proxy.
|
||||
|
||||
Rack evaluates `X-Forwarded-Ssl` and `Forwarded` with a higher priority than `X-Forwarded-Proto`, so
|
||||
an outer proxy could silently override the scheme that Zammad's own web server configuration had set.
|
||||
The nginx and Apache configurations shipped with Zammad therefore now clear `Forwarded`,
|
||||
`X-Forwarded-Scheme` and `X-Forwarded-Ssl`, which leaves `X-Forwarded-Proto` as the only place where
|
||||
the scheme is decided.
|
||||
|
||||
The Apache configurations did not set `X-Forwarded-Proto` at all before, so an instance behind an
|
||||
SSL-terminating Apache was never seen as HTTPS. They now set it explicitly, to `http` in the non-SSL
|
||||
and to `https` in the SSL virtual host, which also means an outer proxy's value is no longer passed
|
||||
through.
|
||||
|
||||
The Docker images rebuild their nginx configuration from the shipped one on every start, so the change
|
||||
applies to them automatically. Package installations keep the web server configuration they already
|
||||
have; only newly installed ones receive the updated files.
|
||||
|
||||
⚠️ If Zammad was reachable over HTTPS only because the proxy in front of it sent `Forwarded` or
|
||||
`X-Forwarded-Ssl` — or, on Apache, `X-Forwarded-Proto` — configure the scheme in Zammad's own web
|
||||
server instead: set `NGINX_SERVER_SCHEME=https` for the Docker images, or the `X-Forwarded-Proto` line
|
||||
in the nginx respectively Apache configuration file.
|
||||
|
||||
**Related issue:** [#6305](https://github.com/zammad/zammad/issues/6305)
|
||||
|
||||
### Links between an object and itself are rejected, and failed link creations answer `422`
|
||||
|
||||
**Who is affected?** Integrations that create links via `POST /api/v1/links/add`.
|
||||
@@ -202,6 +385,29 @@ reverse proxy are blocked again by it.
|
||||
|
||||
## 7.1
|
||||
|
||||
### `X-Forwarded-Proto` takes precedence over `X-Forwarded-Scheme`
|
||||
|
||||
**Who is affected?** Admins running Zammad behind a reverse proxy that terminates TLS and reports the
|
||||
request scheme via the `X-Forwarded-Scheme` header only, such as Nginx Proxy Manager.
|
||||
|
||||
Zammad was updated from Rack 2 to Rack 3, which changed which header decides whether a request is
|
||||
seen as HTTPS: Rack 2 evaluated `X-Forwarded-Scheme` first, Rack 3 evaluates `X-Forwarded-Proto`
|
||||
first. Zammad's own nginx configuration sets `X-Forwarded-Proto` from the scheme of the connection it
|
||||
receives, in the Docker images via the `NGINX_SERVER_SCHEME` variable, which defaults to `$scheme`.
|
||||
Behind a proxy that terminates TLS and forwards plain HTTP, that value is `http`, so such requests
|
||||
are now seen as plain HTTP even though the proxy reported `X-Forwarded-Scheme: https`.
|
||||
|
||||
With the `http_type` setting set to `https`, the session cookie is marked `Secure`, and a `Secure`
|
||||
cookie is not written on a request that is seen as plain HTTP. No session is created at all, and
|
||||
logging in fails with "CSRF token verification failed!". Sessions that already hold a cookie keep
|
||||
working, so the problem only surfaces after a logout.
|
||||
|
||||
⚠️ Configure the scheme in Zammad's own web server rather than in the proxy in front of it, whose
|
||||
`X-Forwarded-Proto` header is overwritten: set `NGINX_SERVER_SCHEME=https` for the Docker images, or
|
||||
the `X-Forwarded-Proto` line in the nginx configuration file.
|
||||
|
||||
**Related issue:** [#6305](https://github.com/zammad/zammad/issues/6305)
|
||||
|
||||
### Elasticsearch 7 was deprecated
|
||||
|
||||
**Who is affected?** Admins of systems that run Elasticsearch 7.
|
||||
|
||||
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,8 +1,8 @@
|
||||
# Change Log for Zammad 7.2.x
|
||||
# Change Log for Zammad 7.3.x
|
||||
|
||||
- [Release notes](https://zammad.com/en/product/releases/zammad-7-2-x)
|
||||
- [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.0.1...7.2.x)
|
||||
- [File tree](https://github.com/zammad/zammad/tree/7.2.x)
|
||||
- [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)
|
||||
|
||||
36
Dockerfile
36
Dockerfile
@@ -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 libpq5 nginx gnupg postgresql-client && \
|
||||
apt-get install -y --no-install-recommends libjemalloc2 curl libimlib2 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
|
||||
@@ -40,12 +40,15 @@ RUN npm -g install corepack && corepack enable pnpm && \
|
||||
FROM base AS build
|
||||
|
||||
ARG COMMIT_SHA
|
||||
# Optional additional build information, e.g. the customer image name when building
|
||||
# images with addon packages. Shown in the version string of the instance.
|
||||
ARG BUILD_LABEL
|
||||
|
||||
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 libpq-dev libyaml-dev && \
|
||||
apt-get install --no-install-recommends -y build-essential git libimlib2-dev libssl-dev libyaml-dev && \
|
||||
rm -rf /var/lib/apt/lists /var/cache/apt/archives
|
||||
|
||||
# Install application gems
|
||||
@@ -71,11 +74,38 @@ RUN if [ -z "${COMMIT_SHA}" ]; then \
|
||||
echo "Error: the required build argument \$COMMIT_SHA is missing."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
if ! [[ "${COMMIT_SHA}" =~ ^[0-9a-fA-F]{8,40}$ ]]; then \
|
||||
echo "Error: the build argument \$COMMIT_SHA must match [0-9a-fA-F]{8,40}, got '${COMMIT_SHA}'."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
if [ -n "${BUILD_LABEL}" ] && ! [[ "${BUILD_LABEL}" =~ ^[0-9a-zA-Z_.-]{1,35}$ ]]; then \
|
||||
echo "Error: the build argument \$BUILD_LABEL must match [0-9a-zA-Z_.-]{1,35}, got '${BUILD_LABEL}'."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
COMMIT_SHA_SHORT=$(echo "${COMMIT_SHA}" | cut -c 1-8); \
|
||||
echo "$(tr -d '\n' < VERSION)-${COMMIT_SHA_SHORT}.docker" > VERSION; \
|
||||
echo "$(tr -d '\n' < VERSION)-${COMMIT_SHA_SHORT}${BUILD_LABEL:+.${BUILD_LABEL}}.docker" > VERSION; \
|
||||
echo 'Updated build information in VERSION:'; \
|
||||
cat VERSION
|
||||
|
||||
# Special handling for zpm addons, if present:
|
||||
# - Unpack addon packages provided in packages/install/*.zpm (registered in the database on container start)
|
||||
# and packages/uninstall/*.zpm (removed from the database on container start).
|
||||
# - Install additional gems provided by addon packages (Gemfile.local.*).
|
||||
# Deployment mode is disabled to allow updating the Gemfile.lock with the additional gems.
|
||||
# - Regenerate the GraphQL frontend API in case addon packages extend the GraphQL schema.
|
||||
# A rake task is used instead of 'rails generate' because rake skips eager loading,
|
||||
# which would require a database connection.
|
||||
RUN if compgen -G "packages/*/*.zpm" > /dev/null; then \
|
||||
ruby script/build/unpack_addon_packages.rb; \
|
||||
if compgen -G "Gemfile.local.*" > /dev/null; then \
|
||||
BUNDLE_DEPLOYMENT=false bundle install && \
|
||||
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache; \
|
||||
fi; \
|
||||
mkdir -p tmp && touch db/schema.rb; \
|
||||
RAILS_LOG_TO_STDOUT= ZAMMAD_SAFE_MODE=1 DATABASE_URL=postgresql://zammad:/zammad ZAMMAD_GRAPHQL_INTROSPECTION=true bundle exec rake zammad:graphql:introspection > app/graphql/graphql_introspection.json; \
|
||||
pnpm exec graphql-codegen -c .graphql_code_generator.js; \
|
||||
fi
|
||||
|
||||
# Don't require Redis or Postgres (use fake DATABASE_URL to make Rails validation happy).
|
||||
RUN touch db/schema.rb && \
|
||||
ZAMMAD_SAFE_MODE=1 DATABASE_URL=postgresql://zammad:/zammad bundle exec rake assets:precompile
|
||||
|
||||
12
Gemfile
12
Gemfile
@@ -4,7 +4,7 @@ source 'https://rubygems.org'
|
||||
|
||||
# core - base
|
||||
ruby '3.4.9'
|
||||
gem 'rails', '~> 8.0.0'
|
||||
gem 'rails', '~> 8.1.0'
|
||||
gem 'rake'
|
||||
|
||||
# core - rails additions
|
||||
@@ -21,6 +21,7 @@ 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'
|
||||
@@ -216,6 +217,7 @@ 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'
|
||||
@@ -227,8 +229,11 @@ group :development, :test do
|
||||
gem 'capybara'
|
||||
gem 'selenium-webdriver'
|
||||
|
||||
# Playwright pilot (throwaway evaluation)
|
||||
# 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.
|
||||
gem 'capybara-playwright-driver'
|
||||
gem 'playwright-ruby-client'
|
||||
|
||||
# code QA
|
||||
gem 'brakeman', require: false
|
||||
@@ -268,9 +273,6 @@ 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.
|
||||
|
||||
|
||||
272
Gemfile.lock
272
Gemfile.lock
@@ -32,29 +32,31 @@ GEM
|
||||
simple_po_parser (~> 1.1.6)
|
||||
aasm (6.0.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
actioncable (8.0.5.1)
|
||||
actionpack (= 8.0.5.1)
|
||||
activesupport (= 8.0.5.1)
|
||||
action_text-trix (2.1.19)
|
||||
railties
|
||||
actioncable (8.1.4)
|
||||
actionpack (= 8.1.4)
|
||||
activesupport (= 8.1.4)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
zeitwerk (~> 2.6)
|
||||
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)
|
||||
actionmailbox (8.1.4)
|
||||
actionpack (= 8.1.4)
|
||||
activejob (= 8.1.4)
|
||||
activerecord (= 8.1.4)
|
||||
activestorage (= 8.1.4)
|
||||
activesupport (= 8.1.4)
|
||||
mail (>= 2.8.0)
|
||||
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)
|
||||
actionmailer (8.1.4)
|
||||
actionpack (= 8.1.4)
|
||||
actionview (= 8.1.4)
|
||||
activejob (= 8.1.4)
|
||||
activesupport (= 8.1.4)
|
||||
mail (>= 2.8.0)
|
||||
rails-dom-testing (~> 2.2)
|
||||
actionpack (8.0.5.1)
|
||||
actionview (= 8.0.5.1)
|
||||
activesupport (= 8.0.5.1)
|
||||
actionpack (8.1.4)
|
||||
actionview (= 8.1.4)
|
||||
activesupport (= 8.1.4)
|
||||
nokogiri (>= 1.8.5)
|
||||
rack (>= 2.2.4)
|
||||
rack-session (>= 1.0.1)
|
||||
@@ -62,27 +64,28 @@ GEM
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
useragent (~> 0.16)
|
||||
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)
|
||||
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)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (8.0.5.1)
|
||||
activesupport (= 8.0.5.1)
|
||||
actionview (8.1.4)
|
||||
activesupport (= 8.1.4)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.11)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
activejob (8.0.5.1)
|
||||
activesupport (= 8.0.5.1)
|
||||
activejob (8.1.4)
|
||||
activesupport (= 8.1.4)
|
||||
globalid (>= 0.3.6)
|
||||
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)
|
||||
activemodel (8.1.4)
|
||||
activesupport (= 8.1.4)
|
||||
activerecord (8.1.4)
|
||||
activemodel (= 8.1.4)
|
||||
activesupport (= 8.1.4)
|
||||
timeout (>= 0.4.0)
|
||||
activerecord-import (2.3.0)
|
||||
activerecord (>= 4.2)
|
||||
@@ -92,20 +95,20 @@ GEM
|
||||
cgi (>= 0.3.6)
|
||||
rack (>= 2.0.8, < 4)
|
||||
railties (>= 7.1)
|
||||
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)
|
||||
activestorage (8.1.4)
|
||||
actionpack (= 8.1.4)
|
||||
activejob (= 8.1.4)
|
||||
activerecord (= 8.1.4)
|
||||
activesupport (= 8.1.4)
|
||||
marcel (~> 1.0)
|
||||
activesupport (8.0.5.1)
|
||||
activesupport (8.1.4)
|
||||
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)
|
||||
@@ -130,8 +133,8 @@ GEM
|
||||
autoprefixer-rails (10.4.21.0)
|
||||
execjs (~> 2)
|
||||
aws-eventstream (1.4.0)
|
||||
aws-partitions (1.1282.0)
|
||||
aws-sdk-core (3.254.1)
|
||||
aws-partitions (1.1292.0)
|
||||
aws-sdk-core (3.257.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
@@ -139,11 +142,12 @@ GEM
|
||||
bigdecimal
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
logger
|
||||
aws-sdk-kms (1.130.0)
|
||||
aws-sdk-core (~> 3, >= 3.254.0)
|
||||
rexml (~> 3.4, >= 3.4.2)
|
||||
aws-sdk-kms (1.132.0)
|
||||
aws-sdk-core (~> 3, >= 3.256.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.229.0)
|
||||
aws-sdk-core (~> 3, >= 3.254.1)
|
||||
aws-sdk-s3 (1.233.1)
|
||||
aws-sdk-core (~> 3, >= 3.256.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.12.1)
|
||||
@@ -153,16 +157,16 @@ GEM
|
||||
samovar (~> 2.1)
|
||||
base64 (0.3.0)
|
||||
benchmark (0.5.0)
|
||||
bigdecimal (4.1.2)
|
||||
bigdecimal (4.1.3)
|
||||
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.25.0)
|
||||
bootsnap (1.26.0)
|
||||
msgpack (~> 1.5)
|
||||
brakeman (8.0.6)
|
||||
brakeman (8.1.0)
|
||||
racc
|
||||
browser (6.2.0)
|
||||
builder (3.3.0)
|
||||
@@ -177,7 +181,7 @@ GEM
|
||||
rack-test (>= 0.6.3)
|
||||
regexp_parser (>= 1.5, < 3.0)
|
||||
xpath (~> 3.2)
|
||||
capybara-playwright-driver (0.5.10)
|
||||
capybara-playwright-driver (0.5.12)
|
||||
addressable
|
||||
capybara
|
||||
playwright-ruby-client (>= 1.16.0)
|
||||
@@ -202,7 +206,7 @@ GEM
|
||||
coffee-script-source (1.12.2)
|
||||
concurrent-ruby (1.3.8)
|
||||
connection_pool (3.0.2)
|
||||
console (1.37.0)
|
||||
console (1.38.0)
|
||||
fiber-annotation
|
||||
fiber-local (~> 1.1)
|
||||
json
|
||||
@@ -215,7 +219,7 @@ GEM
|
||||
crass (1.0.7)
|
||||
csv (3.3.6)
|
||||
daemons (1.4.1)
|
||||
dalli (5.1.0)
|
||||
dalli (5.2.2)
|
||||
logger
|
||||
dartsass-rails (0.5.1)
|
||||
railties (>= 6.0.0)
|
||||
@@ -229,7 +233,7 @@ GEM
|
||||
activesupport (>= 7.0)
|
||||
diff-lcs (1.6.2)
|
||||
diffy (3.4.4)
|
||||
doorkeeper (5.9.6)
|
||||
doorkeeper (5.9.9)
|
||||
railties (>= 5)
|
||||
drb (2.2.3)
|
||||
dry-cli (1.4.1)
|
||||
@@ -286,7 +290,7 @@ GEM
|
||||
railties (>= 6.1.0)
|
||||
faker (3.8.0)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
faraday (2.14.3)
|
||||
faraday (2.14.4)
|
||||
faraday-net_http (>= 2.0, < 3.5)
|
||||
json
|
||||
logger
|
||||
@@ -300,6 +304,10 @@ 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
|
||||
@@ -311,10 +319,22 @@ GEM
|
||||
ostruct
|
||||
globalid (1.4.0)
|
||||
activesupport (>= 6.1)
|
||||
google-protobuf (4.36.0)
|
||||
google-protobuf (4.36.2)
|
||||
bigdecimal
|
||||
rake (~> 13.3)
|
||||
graphql (2.6.10)
|
||||
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)
|
||||
base64
|
||||
fiber-storage
|
||||
logger
|
||||
@@ -330,15 +350,11 @@ 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.4)
|
||||
icalendar (2.12.5)
|
||||
base64
|
||||
ice_cube (~> 0.16)
|
||||
logger
|
||||
@@ -352,14 +368,14 @@ GEM
|
||||
inflection (1.0.0)
|
||||
iniparse (1.5.0)
|
||||
interception (0.5)
|
||||
io-console (0.9.2)
|
||||
io-console (0.9.4)
|
||||
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 (2.21.2)
|
||||
json (3.0.2)
|
||||
json-jwt (1.17.2)
|
||||
activesupport (>= 4.2)
|
||||
aes_key_wrap
|
||||
@@ -367,7 +383,7 @@ GEM
|
||||
bindata
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
jwt (3.2.0)
|
||||
jwt (3.3.0)
|
||||
base64
|
||||
koala (3.7.0)
|
||||
addressable
|
||||
@@ -380,7 +396,7 @@ GEM
|
||||
rexml
|
||||
language_server-protocol (3.17.0.6)
|
||||
lint_roller (1.1.0)
|
||||
listen (3.10.0)
|
||||
listen (3.10.1)
|
||||
logger
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
@@ -410,14 +426,14 @@ GEM
|
||||
mime-types (3.7.0)
|
||||
logger
|
||||
mime-types-data (~> 3.2025, >= 3.2025.0507)
|
||||
mime-types-data (3.2026.0701)
|
||||
mime-types-data (3.2026.0929)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.9)
|
||||
minitest (6.0.6)
|
||||
drb (~> 2.0)
|
||||
prism (~> 1.5)
|
||||
msgpack (1.8.4)
|
||||
multi_json (1.21.1)
|
||||
msgpack (1.8.5)
|
||||
multi_json (1.21.2)
|
||||
multi_xml (0.9.1)
|
||||
bigdecimal (>= 3.1, < 5)
|
||||
multipart-post (2.4.1)
|
||||
@@ -425,7 +441,7 @@ GEM
|
||||
nestful (1.1.4)
|
||||
net-http (0.9.1)
|
||||
uri (>= 0.11.1)
|
||||
net-imap (0.6.6)
|
||||
net-imap (0.6.7)
|
||||
date
|
||||
net-protocol
|
||||
net-ldap (0.20.0)
|
||||
@@ -433,15 +449,23 @@ GEM
|
||||
ostruct
|
||||
net-pop (0.1.2)
|
||||
net-protocol
|
||||
net-protocol (0.3.0)
|
||||
net-protocol (0.4.0)
|
||||
timeout
|
||||
net-smtp (0.5.1)
|
||||
net-smtp (0.5.2)
|
||||
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
|
||||
@@ -480,8 +504,8 @@ GEM
|
||||
omniauth-gitlab (4.1.0)
|
||||
omniauth (~> 2.0)
|
||||
omniauth-oauth2 (~> 1.8.0)
|
||||
omniauth-google-oauth2 (1.2.2)
|
||||
jwt (>= 2.9.2)
|
||||
omniauth-google-oauth2 (1.2.3)
|
||||
jwt (>= 2.9.2, < 4)
|
||||
oauth2 (~> 2.0)
|
||||
omniauth (~> 2.0)
|
||||
omniauth-oauth2 (~> 1.8)
|
||||
@@ -529,16 +553,20 @@ GEM
|
||||
openssl-signature_algorithm (1.3.0)
|
||||
openssl (> 2.0)
|
||||
ostruct (0.6.3)
|
||||
overcommit (0.72.0)
|
||||
overcommit (0.73.0)
|
||||
childprocess (>= 0.6.3, < 6)
|
||||
iniparse (~> 1.4)
|
||||
rexml (>= 3.4.2)
|
||||
parallel (2.1.0)
|
||||
parallel (2.3.0)
|
||||
parser (3.3.12.0)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
pg (1.6.3)
|
||||
playwright-ruby-client (1.62.0)
|
||||
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)
|
||||
base64
|
||||
concurrent-ruby (>= 1.1.6)
|
||||
mime-types (>= 3.0)
|
||||
@@ -592,7 +620,7 @@ GEM
|
||||
base64 (>= 0.1.0)
|
||||
logger (>= 1.6.0)
|
||||
rack (>= 3.0.0, < 4)
|
||||
rack-proxy (1.0.1)
|
||||
rack-proxy (2.0.1)
|
||||
rack (>= 2.0, < 4)
|
||||
rack-session (2.1.2)
|
||||
base64 (>= 0.1.0)
|
||||
@@ -601,20 +629,20 @@ GEM
|
||||
rack (>= 1.3)
|
||||
rackup (2.3.1)
|
||||
rack (>= 3)
|
||||
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)
|
||||
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)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 8.0.5.1)
|
||||
railties (= 8.1.4)
|
||||
rails-dom-testing (2.3.0)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
@@ -622,9 +650,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.0.5.1)
|
||||
actionpack (= 8.0.5.1)
|
||||
activesupport (= 8.0.5.1)
|
||||
railties (8.1.4)
|
||||
actionpack (= 8.1.4)
|
||||
activesupport (= 8.1.4)
|
||||
irb (~> 1.13)
|
||||
rackup (>= 1.0.0)
|
||||
rake (>= 12.2)
|
||||
@@ -641,7 +669,7 @@ GEM
|
||||
prism (>= 1.6.0)
|
||||
tsort
|
||||
rchardet (1.10.2)
|
||||
rdoc (8.0.0)
|
||||
rdoc (8.1.0)
|
||||
erb
|
||||
prism (>= 1.6.0)
|
||||
rbs (>= 4.0.0)
|
||||
@@ -650,7 +678,7 @@ GEM
|
||||
redis-client (>= 0.22.0)
|
||||
redis-client (0.30.1)
|
||||
connection_pool
|
||||
regexp_parser (2.12.0)
|
||||
regexp_parser (2.13.1)
|
||||
reline (0.7.0)
|
||||
io-console (~> 0.5)
|
||||
rexml (3.4.4)
|
||||
@@ -674,8 +702,10 @@ 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.90.0)
|
||||
rubocop (1.91.0)
|
||||
json (>= 2.3)
|
||||
language_server-protocol (~> 3.17.0.2)
|
||||
lint_roller (~> 1.1.0)
|
||||
@@ -710,7 +740,7 @@ GEM
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (>= 1.89.0, < 2.0)
|
||||
rubocop-ast (>= 1.47.1, < 2.0)
|
||||
rubocop-rails (2.37.0)
|
||||
rubocop-rails (2.38.0)
|
||||
activesupport (>= 4.2.0)
|
||||
lint_roller (~> 1.1)
|
||||
rack (>= 1.1)
|
||||
@@ -727,9 +757,6 @@ 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)
|
||||
@@ -742,27 +769,34 @@ GEM
|
||||
ruby-saml (1.18.1)
|
||||
nokogiri (>= 1.13.10)
|
||||
rexml
|
||||
rubyntlm (0.6.5)
|
||||
rubyntlm (0.6.8)
|
||||
base64
|
||||
rubyzip (3.5.0)
|
||||
rubyzip (3.7.0)
|
||||
safety_net_attestation (0.5.0)
|
||||
jwt (>= 2.0, < 4.0)
|
||||
samovar (2.5.1)
|
||||
console (~> 1.0)
|
||||
sass-embedded (1.103.1)
|
||||
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)
|
||||
google-protobuf (~> 4.31)
|
||||
rake (>= 13)
|
||||
securerandom (0.4.1)
|
||||
selenium-webdriver (4.48.0)
|
||||
selenium-webdriver (4.50.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.2.3)
|
||||
simpleidn (0.3.0)
|
||||
slack-ruby-client (3.2.0)
|
||||
faraday (>= 2.0.1)
|
||||
faraday-mashify
|
||||
@@ -807,7 +841,7 @@ GEM
|
||||
openssl (> 2.0)
|
||||
openssl-signature_algorithm (~> 1.0)
|
||||
tsort (0.2.0)
|
||||
twilio-ruby (7.11.1)
|
||||
twilio-ruby (7.11.3)
|
||||
faraday (>= 2.0, < 3.0)
|
||||
jwt (>= 1.5, < 4.0)
|
||||
nokogiri (>= 1.6, < 2.0)
|
||||
@@ -818,11 +852,11 @@ GEM
|
||||
tzinfo
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
tzinfo-data (1.2026.3)
|
||||
tzinfo-data (1.2026.5)
|
||||
tzinfo (>= 1.0.0)
|
||||
unicode-display_width (3.2.0)
|
||||
unicode-emoji (~> 4.1)
|
||||
unicode-emoji (4.2.0)
|
||||
unicode-display_width (3.3.0)
|
||||
unicode-emoji (~> 4.3)
|
||||
unicode-emoji (4.3.0)
|
||||
uri (1.1.1)
|
||||
useragent (0.16.11)
|
||||
validate_url (1.0.15)
|
||||
@@ -840,7 +874,7 @@ GEM
|
||||
vite_rails (3.11.1)
|
||||
railties (>= 5.1, < 9)
|
||||
vite_ruby (~> 3.0, >= 3.2.2)
|
||||
vite_ruby (3.10.5)
|
||||
vite_ruby (3.11.1)
|
||||
dry-cli (>= 0.7, < 2)
|
||||
logger (~> 1.6)
|
||||
mutex_m
|
||||
@@ -867,11 +901,11 @@ GEM
|
||||
base64
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
whatsapp_sdk (1.1.0)
|
||||
whatsapp_sdk (1.2.0)
|
||||
faraday (~> 2.0, > 2.0.1)
|
||||
faraday-multipart (~> 1)
|
||||
zeitwerk (~> 2)
|
||||
write_xlsx (1.15.0)
|
||||
write_xlsx (1.15.1)
|
||||
nkf
|
||||
rubyzip (>= 2.4.0, < 4.0)
|
||||
xpath (3.2.0)
|
||||
@@ -888,7 +922,11 @@ GEM
|
||||
multipart-post (~> 2.0)
|
||||
|
||||
PLATFORMS
|
||||
aarch64-linux-gnu
|
||||
arm64-darwin
|
||||
ruby
|
||||
x86_64-darwin
|
||||
x86_64-linux-gnu
|
||||
|
||||
DEPENDENCIES
|
||||
PoParser
|
||||
@@ -900,6 +938,7 @@ DEPENDENCIES
|
||||
autodiscover!
|
||||
autoprefixer-rails
|
||||
aws-sdk-s3
|
||||
benchmark
|
||||
biz
|
||||
bootsnap
|
||||
brakeman
|
||||
@@ -965,6 +1004,7 @@ DEPENDENCIES
|
||||
overcommit
|
||||
parallel
|
||||
pg (~> 1.5, >= 1.5.4)
|
||||
playwright-ruby-client
|
||||
pry-doc
|
||||
pry-rails
|
||||
pry-remote
|
||||
@@ -975,13 +1015,14 @@ DEPENDENCIES
|
||||
pundit
|
||||
pundit-matchers
|
||||
rack-attack
|
||||
rails (~> 8.0.0)
|
||||
rails (~> 8.1.0)
|
||||
rake
|
||||
rchardet (>= 1.8.0)
|
||||
redis (< 6)
|
||||
rotp
|
||||
rspec-rails
|
||||
rspec-retry
|
||||
rspec_junit_formatter
|
||||
rszr
|
||||
rubocop
|
||||
rubocop-capybara
|
||||
@@ -994,7 +1035,6 @@ DEPENDENCIES
|
||||
rubocop-rake
|
||||
rubocop-rspec
|
||||
rubocop-rspec_rails
|
||||
ruby-keycloak-admin
|
||||
ruby-lsp
|
||||
ruby-lsp-rails
|
||||
ruby-lsp-rspec
|
||||
|
||||
@@ -133,6 +133,12 @@ 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
|
||||
|
||||
@@ -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.
|
||||
# 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.
|
||||
renderProviderModal: (identifier) =>
|
||||
return unless identifier
|
||||
return unless App.Config.get('ai_provider')
|
||||
return unless @permissionCheck('admin.ai_provider')
|
||||
|
||||
@el.find('.js-featureProviderButton').remove()
|
||||
|
||||
@@ -41,27 +41,15 @@ class App.ControllerAIFeatureProviderModal extends App.ControllerModal
|
||||
@controller?.releaseController()
|
||||
@controller = new App.ControllerForm(
|
||||
model: { configure_attributes: @buildProviderAttributes() }
|
||||
params: @row
|
||||
params: { provider_connection_id: App.AIFeatureProvider.valueFor(@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: options }
|
||||
{ name: 'provider_connection_id', display: __('Provider'), tag: 'select', null: false, options: App.AIFeatureProvider.options(@providers) }
|
||||
]
|
||||
|
||||
onSubmit: (e) =>
|
||||
@@ -71,49 +59,20 @@ class App.ControllerAIFeatureProviderModal extends App.ControllerModal
|
||||
@formDisable(e)
|
||||
params = @formParam(e.target)
|
||||
|
||||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
)
|
||||
|
||||
@@ -57,7 +57,9 @@ class App.AIFeedbackWidget extends App.Controller
|
||||
submitPositiveReaction: (e) ->
|
||||
@preventDefault(e)
|
||||
|
||||
@recordUsage(rating: true, null, =>
|
||||
@recordUsage(rating: true, null, (data) =>
|
||||
return false if @feedbackAlreadyProvided(data)
|
||||
|
||||
@hideAcknowledgment()
|
||||
@showQuestionAndButtons()
|
||||
)
|
||||
@@ -68,9 +70,14 @@ class App.AIFeedbackWidget extends App.Controller
|
||||
submitNegativeReaction: (e) ->
|
||||
@preventDefault(e)
|
||||
|
||||
@recordUsage(rating: false, null, =>
|
||||
@recordUsage(rating: false, null, (data) =>
|
||||
@hideComment()
|
||||
@showToolbar()
|
||||
|
||||
if @feedbackAlreadyProvided(data)
|
||||
@showAcknowledgment()
|
||||
return false
|
||||
|
||||
@showQuestionAndButtons()
|
||||
)
|
||||
|
||||
@@ -94,7 +101,9 @@ class App.AIFeedbackWidget extends App.Controller
|
||||
@preventDefault(e)
|
||||
|
||||
if commentText = @comment.find('textarea').val()
|
||||
@recordUsage(comment: commentText, null, =>
|
||||
@recordUsage(comment: commentText, null, (data) =>
|
||||
return false if @feedbackAlreadyProvided(data)
|
||||
|
||||
@hideAcknowledgment()
|
||||
@hideToolbar()
|
||||
@showComment()
|
||||
@@ -104,6 +113,10 @@ class App.AIFeedbackWidget extends App.Controller
|
||||
@showToolbar()
|
||||
@showAcknowledgment()
|
||||
|
||||
# Feedback may have been given meanwhile in another view of the same result.
|
||||
feedbackAlreadyProvided: (data) ->
|
||||
!!data.responseJSON?.feedback_already_provided
|
||||
|
||||
showQuestionAndButtons: ->
|
||||
@question.removeClass('hide')
|
||||
@buttons.removeClass('hide')
|
||||
|
||||
@@ -27,38 +27,10 @@ class FeedbackAndLogs extends App.ControllerAIFeatureBase
|
||||
# Feedback & Logs works independently of any configured provider connection.
|
||||
showAlert: -> false
|
||||
|
||||
sendDownloadRequest: (type) ->
|
||||
isFeedback = type is 'with_usages'
|
||||
downloadFeedback: ->
|
||||
App.AIAnalyticsDownload.request(type: 'with_usages', button: @$('.js-downloadFeedback'))
|
||||
|
||||
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')
|
||||
downloadErrorLogs: ->
|
||||
App.AIAnalyticsDownload.request(type: 'errors', button: @$('.js-downloadErrorLogs'))
|
||||
|
||||
App.Config.set('FeedbackAndLogs', { prio: 1060, name: __('Feedback & Logs'), parent: '#ai', target: '#ai/feedback_logs', controller: FeedbackAndLogs, permission: ['admin.ai_feedback_logs'] }, 'NavBarAdmin')
|
||||
|
||||
@@ -513,18 +513,27 @@ ProviderConnectionFormMixin =
|
||||
# Type and name head the credential step, and only it: the provider is picked before anything
|
||||
# is fetched with it, and the model step must not offer to change it behind the fetched list.
|
||||
providerSelectionAttributes: (providerKey, excludeProviders) ->
|
||||
sortedOptions = {}
|
||||
Object
|
||||
.entries(App.Config.get('AIProviders'))
|
||||
.filter(([key, _]) -> key not in excludeProviders)
|
||||
.sort(([_, a], [__, b]) -> a.prio - b.prio)
|
||||
.forEach(([key, { label }]) -> sortedOptions[key] = label)
|
||||
attributes = []
|
||||
|
||||
# Provider comes first: selecting it pre-fills the name below.
|
||||
[
|
||||
{ name: 'provider', display: __('Type'), tag: 'select', options: sortedOptions, null: false, nulloption: true, value: providerKey, customsort: 'on' }
|
||||
if providerKey isnt 'zammad_ai' or not App.Config.get('system_online_service')
|
||||
sortedOptions = {}
|
||||
|
||||
Object
|
||||
.entries(App.Config.get('AIProviders'))
|
||||
.filter(([key, _]) -> key not in excludeProviders)
|
||||
.sort(([_, a], [__, b]) -> a.prio - b.prio)
|
||||
.forEach(([key, { label }]) -> sortedOptions[key] = label)
|
||||
|
||||
# Provider comes first: selecting it pre-fills the name below.
|
||||
attributes.push(
|
||||
{ name: 'provider', display: __('Type'), tag: 'select', options: sortedOptions, null: false, nulloption: true, value: providerKey, customsort: 'on' }
|
||||
)
|
||||
|
||||
attributes.push(
|
||||
{ name: 'name', display: __('Name'), tag: 'input', type: 'text', null: false, limit: 100, autocomplete: 'one-time-code' }
|
||||
]
|
||||
)
|
||||
|
||||
attributes
|
||||
|
||||
# The provider fields are built dynamically ('config.token', ...), so they are not part of
|
||||
# App.AIProviderConnection.configure_attributes — App.Model#validate resolves the attributes
|
||||
@@ -646,8 +655,8 @@ class ProviderConnectionCredentials extends App.ControllerModal
|
||||
@buttonSubmit = if lastStep then __('Submit') else __('Next')
|
||||
@buttonClass = if lastStep then 'btn--success' else 'btn--primary'
|
||||
|
||||
# Only when creating: an existing Zammad AI connection stays editable on SaaS.
|
||||
excludeProviders = if !@id and App.Config.get('system_online_service') then ['zammad_ai'] else []
|
||||
# Provider dropdown: hide Zammad AI from the list in SaaS.
|
||||
excludeProviders = if App.Config.get('system_online_service') then ['zammad_ai'] else []
|
||||
|
||||
# Merge unsaved edits (captured on a provider change, and on the way back from the model step)
|
||||
# over the persisted values, so switching the provider keeps them for all compatible fields.
|
||||
@@ -680,6 +689,13 @@ class ProviderConnectionCredentials extends App.ControllerModal
|
||||
@update()
|
||||
@$('[name=name]').focus()
|
||||
|
||||
if @providerKey is 'zammad_ai' and App.Config.get('system_online_service')
|
||||
$('<div />')
|
||||
.addClass('alert alert--warning')
|
||||
.attr('role', 'alert')
|
||||
.text(App.i18n.translatePlain('The Zammad AI provider is managed by the platform and cannot be fully edited here.'))
|
||||
.prependTo(@controller.form)
|
||||
|
||||
@controller.form
|
||||
|
||||
onSubmit: (e) =>
|
||||
|
||||
@@ -67,34 +67,18 @@ class TextTool extends App.ControllerAIFeatureBase
|
||||
|
||||
@controllerBind('config_update', @configHasChanged)
|
||||
|
||||
downloadFeedbackReport: (id) =>
|
||||
downloadFeedbackReport: (id) ->
|
||||
text_tool = App.AITextTool.find(id)
|
||||
|
||||
url = "#{@apiPath}/ai/analytics/download/with_usages?filters[triggered_by_type]=AI::TextTool&filters[triggered_by_id]=#{id}"
|
||||
filters =
|
||||
triggered_by_type: 'AI::TextTool'
|
||||
triggered_by_id: id
|
||||
|
||||
# Only limit the report to feedback since the last reset if one happened;
|
||||
# otherwise the report should collect everything.
|
||||
if text_tool.analytics_stats_reset_at
|
||||
url += "&filters[created_after]=#{text_tool.analytics_stats_reset_at}"
|
||||
filters.created_after = text_tool.analytics_stats_reset_at if text_tool.analytics_stats_reset_at
|
||||
|
||||
@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.')
|
||||
)
|
||||
)
|
||||
App.AIAnalyticsDownload.request(type: 'with_usages', filters: filters)
|
||||
|
||||
resetFeedbackTimestamp: (id) =>
|
||||
@ajax(
|
||||
|
||||
@@ -40,6 +40,12 @@ class App.Controller extends Spine.Controller
|
||||
oldLocation = history[history.length-2]
|
||||
if oldLocation
|
||||
window.history.replaceState(null, null, oldLocation)
|
||||
else
|
||||
# Direct entry, e. g. a bookmark: there is no previous route to fall back to, so
|
||||
# replace the current entry instead of adding one. The hash still changes, so the
|
||||
# target route is executed by the resulting hashchange event.
|
||||
window.location.replace(location.replace(/^#*/, '#'))
|
||||
return
|
||||
super location
|
||||
|
||||
preventDefault: (e) ->
|
||||
|
||||
@@ -18,6 +18,7 @@ class App.ControllerModal extends App.Controller
|
||||
buttonSubmit: true
|
||||
includeForm: true
|
||||
headPrefix: ''
|
||||
headMarkup: true
|
||||
headIcon: null
|
||||
headIconClass: null
|
||||
shown: true
|
||||
@@ -77,6 +78,7 @@ class App.ControllerModal extends App.Controller
|
||||
modal = $(App.view('modal')(
|
||||
head: @head
|
||||
headPrefix: @headPrefix
|
||||
headMarkup: @headMarkup
|
||||
headIcon: @headIcon
|
||||
headIconClass: @headIconClass
|
||||
message: @message
|
||||
|
||||
@@ -35,6 +35,7 @@ class App.ControllerTabs extends App.Controller
|
||||
|
||||
@html App.view('generic/tabs')(
|
||||
header: @header
|
||||
description: @description
|
||||
subHeader: @subHeader
|
||||
tabs: displayTabs
|
||||
addTab: @addTab
|
||||
|
||||
@@ -0,0 +1,368 @@
|
||||
# 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'
|
||||
)
|
||||
@@ -62,13 +62,10 @@ 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: configure_attributes,
|
||||
configure_attributes: @userSelectorAttributes()
|
||||
params:
|
||||
user_ids: []
|
||||
autofocus: false
|
||||
@@ -99,18 +96,35 @@ 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: configure_attributes,
|
||||
configure_attributes: @userSelectorAttributes()
|
||||
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
|
||||
|
||||
@@ -24,6 +24,16 @@ 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()
|
||||
|
||||
@@ -27,6 +27,10 @@ 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']
|
||||
|
||||
@@ -81,7 +81,7 @@ class App.UiElement.richtext.additions.RichTextToolPopupVideo extends App.UiElem
|
||||
if !parsed.host
|
||||
return "( widget: video, provider: #{parsed.provider}, id: #{parsed.id} )"
|
||||
|
||||
hostAllowed = _.some(App.Config.get('kb_self_hosted_video_servers'), (server) -> server.host == parsed.host)
|
||||
hostAllowed = App.KnowledgeBaseVideo.hostAllowed(parsed.host)
|
||||
|
||||
if !hostAllowed
|
||||
@applyError(__('Video server not allowed. Please add to the list of allowed video servers.'))
|
||||
|
||||
@@ -19,7 +19,7 @@ class App.UiElement.sla_times
|
||||
|
||||
# apply hour picker
|
||||
item.find('.js-timeConvertFrom').timepicker(
|
||||
maxHours: 999
|
||||
maxHours: 99999
|
||||
)
|
||||
|
||||
# disable/enable rows
|
||||
|
||||
@@ -866,7 +866,7 @@ class Router extends App.ControllerPermanent
|
||||
split = "/query/#{params.query}"
|
||||
|
||||
id = Math.floor( Math.random() * 99999 )
|
||||
@navigate "#ticket/create/id/#{id}#{split}"
|
||||
@navigate "#ticket/create/id/#{id}#{split}", { hideCurrentLocationFromHistory: true }
|
||||
return
|
||||
|
||||
# check authentication
|
||||
|
||||
@@ -72,6 +72,7 @@ class SidebarCustomer extends App.Controller
|
||||
object: __('User')
|
||||
objects: __('Users')
|
||||
container: @elSidebar.closest('.content')
|
||||
handlers: [App.FormHandlerUserPasswordChangeHint.run]
|
||||
)
|
||||
|
||||
App.Config.set('200-Customer', SidebarCustomer, 'TicketCreateSidebar')
|
||||
|
||||
@@ -120,6 +120,11 @@ 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)
|
||||
@@ -253,6 +258,7 @@ 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'
|
||||
@@ -267,6 +273,7 @@ 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'
|
||||
@@ -289,6 +296,7 @@ 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'
|
||||
|
||||
@@ -35,7 +35,7 @@ class Success extends App.ControllerAppContent
|
||||
detail: __('Woo hoo! Your email address has been verified!')
|
||||
)
|
||||
delay = =>
|
||||
App.Auth.loginCheck(=> @navigate '#')
|
||||
@navigate '#login'
|
||||
@delay(delay, 2000)
|
||||
|
||||
class Fail extends App.ControllerAppContent
|
||||
|
||||
@@ -6,6 +6,11 @@ class GettingStartedAgent extends App.ControllerWizardFullScreen
|
||||
super
|
||||
@authenticateCheckRedirect()
|
||||
|
||||
# redirect if we are not admin
|
||||
if !@permissionCheck('admin.wizard')
|
||||
@navigate '#'
|
||||
return
|
||||
|
||||
# set title
|
||||
@title __('Invite Agents')
|
||||
@fetch()
|
||||
|
||||
@@ -2,6 +2,12 @@ class Import extends App.ControllerWizardFullScreen
|
||||
constructor: ->
|
||||
super
|
||||
@title __('Import')
|
||||
|
||||
# redirect to login if admin user already exists
|
||||
if @Config.get('system_init_done')
|
||||
@navigate '#login'
|
||||
return
|
||||
|
||||
@fetch()
|
||||
|
||||
fetch: ->
|
||||
@@ -14,11 +20,6 @@ class Import extends App.ControllerWizardFullScreen
|
||||
processData: true,
|
||||
success: (data, status, xhr) =>
|
||||
|
||||
# redirect to login if admin user already exists
|
||||
if @Config.get('system_init_done')
|
||||
@navigate '#login'
|
||||
return
|
||||
|
||||
if data.import_mode == true
|
||||
@navigate '#import/' + data.import_backend, { emptyEl: true }
|
||||
return
|
||||
|
||||
@@ -15,6 +15,10 @@ class App.KnowledgeBaseAgentController extends App.Controller
|
||||
|
||||
App.Event.bind 'kb_visibility_may_have_changed', @visibilityMayHaveChanged
|
||||
|
||||
# The role collection push is the only signal a role's knowledge base permissions or its active
|
||||
# flag changed, both of which App.KnowledgeBaseAccess resolves the access from.
|
||||
@listenTo App.Role, 'refresh', @accessMayHaveChanged
|
||||
|
||||
if @permissionCheck('knowledge_base.*') and App.Config.get('kb_active')
|
||||
@updateNavMenu()
|
||||
else if App.Config.get('kb_active_publicly')
|
||||
@@ -191,6 +195,9 @@ class App.KnowledgeBaseAgentController extends App.Controller
|
||||
renderControllers: (params) ->
|
||||
object = @constructor.pickObjectUsing(params, @)
|
||||
|
||||
# What the screen below is being built against — see #accessMayHaveChanged.
|
||||
@renderedAccessSignature = @roleAccessSignature()
|
||||
|
||||
if !object || (!@isEditor() && !object.visibleInternally(@kb_locale()))
|
||||
@renderNotFound()
|
||||
return
|
||||
@@ -417,6 +424,48 @@ class App.KnowledgeBaseAgentController extends App.Controller
|
||||
if didRemove
|
||||
@notifyVisibilityChangeLoaded()
|
||||
|
||||
# A role losing a knowledge base permission, or being deactivated, changes what the user may do
|
||||
# with categories and answers that did not change themselves: nothing sends `kb_data_changed`
|
||||
# for any of them, and the id sets #visibilityMayHaveChanged diffs stay equal whenever the
|
||||
# access only drops from editor to reader. Building the screen again is what applies the new
|
||||
# access — the content controller reads it once, when it is built, and hands it down to the
|
||||
# items it renders.
|
||||
accessMayHaveChanged: =>
|
||||
return if !@renderedAccessSignature?
|
||||
return if @renderedAccessSignature is @roleAccessSignature()
|
||||
|
||||
@updateNavMenu()
|
||||
|
||||
# Refetched either way, because which categories and answers the user may see grows with a
|
||||
# raised access and shrinks with a lowered one, and no assets accompany a role change.
|
||||
#
|
||||
# Built again only while on display: #show leaves an edit route the access no longer allows,
|
||||
# which must not happen behind the back of a user working elsewhere. Dropping the url of the
|
||||
# content controller is what defers the rebuild to their return.
|
||||
@contentController?.url = null
|
||||
@pendingParams = @lastParams if @active()
|
||||
|
||||
@fetch(false, @active())
|
||||
|
||||
# The knowledge base access every role of the current user grants, as App.KnowledgeBaseAccess
|
||||
# reads it: the granular permissions arrive as ordinary asset changes, the role's own
|
||||
# permissions and its active flag do not.
|
||||
roleAccessSignature: ->
|
||||
kb_permission_ids = ['knowledge_base.reader', 'knowledge_base.editor']
|
||||
.map (name) -> App.Permission.findByAttribute('name', name)?.id
|
||||
|
||||
App.User
|
||||
.current()
|
||||
.role_ids
|
||||
.map (role_id) ->
|
||||
role = App.Role.find(role_id)
|
||||
|
||||
return "#{role_id}:inactive" if !role?.active
|
||||
|
||||
"#{role_id}:#{_.intersection(role.permission_ids, kb_permission_ids).sort().join(',')}"
|
||||
.sort()
|
||||
.join('|')
|
||||
|
||||
@pickObjectUsing: (params, parentController) ->
|
||||
kb = parentController.getKnowledgeBase()
|
||||
return if !kb
|
||||
|
||||
@@ -102,7 +102,7 @@ class App.KnowledgeBaseReaderController extends App.Controller
|
||||
renderBody: (translation) ->
|
||||
body = translation.content().body
|
||||
body = @prepareLinks(body)
|
||||
body = @prepareVideos(body)
|
||||
body = App.KnowledgeBaseReaderController.prepareVideos(body)
|
||||
|
||||
@answerBody.html(body)
|
||||
@bindInlineImagePreview()
|
||||
@@ -128,32 +128,56 @@ class App.KnowledgeBaseReaderController extends App.Controller
|
||||
|
||||
$('<container>').append(input).html()
|
||||
|
||||
prepareVideos: (input) ->
|
||||
# An admin-approved server may carry an explicit port (see
|
||||
# Setting::Validation::KbSelfHostedVideoServers), which must stay a literal ':'
|
||||
# delimiter - escaping it would point the iframe at a host that does not exist, and
|
||||
# no longer match the origin allowed via the CSP frame-src. The host name itself is
|
||||
# still escaped, so a value that somehow entered the setting without passing its
|
||||
# validation cannot break out of the surrounding attribute.
|
||||
@escapeVideoHost: (host) ->
|
||||
index = host.lastIndexOf(':')
|
||||
name = host.slice(0, index)
|
||||
port = host.slice(index + 1)
|
||||
|
||||
return encodeURIComponent(host) if index < 1 || !/^\d+$/.test(port)
|
||||
|
||||
"#{encodeURIComponent(name)}:#{port}"
|
||||
|
||||
@prepareVideos: (input) ->
|
||||
input.replace /\(([\s]*)widget:([\s]*)video[\W]([\s\S])+?\)/g, (match) ->
|
||||
settings = match
|
||||
.slice(1, -1)
|
||||
.split(',')
|
||||
.map (pair) -> pair.split(':').map (elem) -> elem.trim()
|
||||
.map (pair) ->
|
||||
[key, rest...] = pair.split(':')
|
||||
[key.trim(), rest.join(':').trim()]
|
||||
.reduce (memo, elem) ->
|
||||
memo[elem[0]] = elem[1]
|
||||
return memo
|
||||
, {}
|
||||
|
||||
id = encodeURIComponent(settings.id ? '')
|
||||
host = App.KnowledgeBaseReaderController.escapeVideoHost(settings.host ? '')
|
||||
|
||||
hostAllowed = App.KnowledgeBaseVideo.hostAllowed(settings.host)
|
||||
|
||||
# coffeelint: disable=indentation
|
||||
url = switch settings.provider
|
||||
when 'youtube'
|
||||
"https://www.youtube.com/embed/#{settings.id}"
|
||||
"https://www.youtube.com/embed/#{id}"
|
||||
when 'vimeo'
|
||||
"https://player.vimeo.com/video/#{settings.id}"
|
||||
"https://player.vimeo.com/video/#{id}"
|
||||
when 'peertube'
|
||||
"https://#{settings.host}/videos/embed/#{settings.id}"
|
||||
"https://#{host}/videos/embed/#{id}" if hostAllowed
|
||||
when 'mediacms'
|
||||
"https://#{settings.host}/embed?m=#{settings.id}"
|
||||
"https://#{host}/embed?m=#{id}" if hostAllowed
|
||||
# coffeelint: enable=indentation
|
||||
|
||||
return match unless url
|
||||
return '' if !url
|
||||
|
||||
"<div class='videoWrapper'><iframe allowfullscreen id='#{settings.provider}#{settings.id}' type='text/html' src='#{url}' frameborder='0'></iframe></div>"
|
||||
idAttribute = App.Utils.htmlEscape("#{settings.provider}#{settings.id ? ''}")
|
||||
|
||||
"<div class='videoWrapper'><iframe allowfullscreen id='#{idAttribute}' type='text/html' src='#{App.Utils.htmlEscape(url)}' frameborder='0'></iframe></div>"
|
||||
|
||||
renderAttachments: (attachments) ->
|
||||
@answerAttachments.html App.view('generic/attachments')(
|
||||
|
||||
@@ -66,7 +66,8 @@ class Login extends App.ControllerFullPage
|
||||
data.name != 'product_name' &&
|
||||
data.name != 'product_logo' &&
|
||||
data.name != 'fqdn' &&
|
||||
data.name != 'user_show_password_login'
|
||||
data.name != 'user_show_password_login' &&
|
||||
data.name != 'import_mode'
|
||||
@render()
|
||||
'rerender'
|
||||
)
|
||||
|
||||
@@ -34,6 +34,7 @@ class Package extends App.ControllerSubContent
|
||||
)
|
||||
|
||||
render: ->
|
||||
actions_available = App.Config.get('package_actions_enabled')
|
||||
|
||||
for item in @packages
|
||||
item.action = []
|
||||
@@ -45,6 +46,7 @@ class Package extends App.ControllerSubContent
|
||||
else if item.state == 'deactivate'
|
||||
item.action = ['uninstall', 'activate']
|
||||
item.action = ['uninstall']
|
||||
continue if !actions_available
|
||||
if @api_package_metas[item.name] && item.vendor is @api_package_metas[item.name].vendor
|
||||
if @isNewerVersion(item.version, @api_package_metas[item.name].version)
|
||||
item.action.unshift('update_api')
|
||||
@@ -63,6 +65,7 @@ class Package extends App.ControllerSubContent
|
||||
token_present: @token_present
|
||||
api_package_metas: @api_package_metas
|
||||
api_packages_installable: api_packages_installable
|
||||
actions_available: actions_available
|
||||
)
|
||||
|
||||
uninstall: (e) ->
|
||||
@@ -207,4 +210,4 @@ class PackageSettingsModal extends App.ControllerModal
|
||||
super
|
||||
@parent.load()
|
||||
|
||||
App.Config.set('Packages', { prio: 3700, name: __('Packages'), parent: '#system', target: '#system/package', controller: Package, permission: (controller) -> App.Config.get('admin_packages') && controller.permissionCheck('admin.package') }, 'NavBarAdmin')
|
||||
App.Config.set('Packages', { prio: 3700, name: __('Packages'), parent: '#system', target: '#system/package', controller: Package, permission: (controller) -> controller.permissionCheck('admin.package') }, 'NavBarAdmin')
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user