From 91d1073b5e7fe376bec2dbe72135e67950c7d375 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 17:58:14 +0000 Subject: [PATCH 01/22] fix(folders): refuse DROP FOLDER on a non-empty folder (#892) DROP FOLDER's contract -- "the folder must be empty (no child documents or sub-folders)" -- existed only in a doc comment. Nothing checked, so the command called DeleteFolder unconditionally and left every document inside pointing at a container that no longer existed. Nothing was deleted. The documents were *orphaned*: they survive as units but lose their module qualification, so FeedbackModule.IMM_PostResponse becomes .IMM_PostResponse, nothing can resolve them, and mxbuild reports CE1613 "no longer exists". That distinction matters -- the data is recoverable by re-parenting, not lost -- and it means the fix is "refuse or re-parent", not "stop cascading a delete". Reproduces on a STOCK blank app: FeedbackModule ships Private/Resources/Mappings holding two JSON structures, one import mapping and one export mapping. The guard reads ctx.Backend.ListUnits(), NOT the per-kind lists that LIST FOLDERS renders from. That is load-bearing: documentsByContainer is a hand-maintained list of twelve document kinds, and the four documents above are none of them -- which is why the folder rendered as [0] and made the drop look safe. A guard built on the same list would inherit the same blind spot and wave through exactly the kinds it forgets. ListUnits is type-agnostic, so it cannot. Folders are units too (Projects$Folder), so one containment scan covers sub-folders as well. It also fails closed: if ListUnits errors, the drop is refused. For a destructive operation "I could not check" must never mean "go ahead". Separately adds the five missing kinds to documentsByContainer so the count stops lying -- the folder now reports [4] and names all four. Verified end to end on a blank 11.13 app: the drop is refused, the four documents keep their module qualification, and mx check reports 0 errors. Control run with the guard stubbed fails exactly the three guard tests and passes with it restored. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013uQvFDd5R4eNqqita59jM8 --- .claude/skills/fix-issue.md | 1 + CHANGELOG.md | 5 + .../892-drop-folder-not-empty.fail.mdl | 30 ++++++ mdl/executor/cmd_folders.go | 81 +++++++++++++++- mdl/executor/cmd_folders_mock_test.go | 93 +++++++++++++++++++ mdl/executor/cmd_list_folders.go | 28 ++++++ mdl/executor/cmd_list_folders_test.go | 38 ++++++++ 7 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 mdl-examples/bug-tests/892-drop-folder-not-empty.fail.mdl diff --git a/.claude/skills/fix-issue.md b/.claude/skills/fix-issue.md index 7bbcb07fb..96afd0355 100644 --- a/.claude/skills/fix-issue.md +++ b/.claude/skills/fix-issue.md @@ -515,3 +515,4 @@ extracting `OffsetExpression`/`LimitExpression`. | `DESCRIBE PAGE` output will not re-parse for any page carrying a **pluggable widget**: `mxcli check` on it fails with `extraneous input ':'` / `extraneous input '('` from the first widget onward. Separately, a boolean property the author set is missing from the description entirely | Two independent defects in the same path. **Emit**: explicit properties were written with a raw `%s`, so every string lost its quotes — a JSON `spec: {"a": 1}` then broke the parse at its first brace. **Read**: `extractExplicitProperties` skipped any value of `"true"`/`"false"` as a "common default", so booleans never reached the output | `mdl/executor/cmd_pages_describe_output.go` (`explicitPropValue`, `isBareLiteral`), `mdl/executor/cmd_pages_describe_pluggable.go` (`buildPropertyValueTypeMap`, `extractExplicitProperties`), `mdl/executor/cmd_pages_describe.go` (`rawExplicitProp.ValueType`) | **Fixing one half alone is worse than the bug.** Quote without emitting booleans and the description re-parses cleanly while silently dropping a property — a wrong page that validates. Both halves ship together or neither. **Quote by the DECLARED type, never the value's shape**: `ValueType.Type` sits in the widget's `Type.ObjectType.PropertyTypes`, the same array `buildPropertyTypeKeyMap` already walks for `PropertyKey` and throws away; a String property holding `"30"` or `"true"` is indistinguishable from a number once it is a string in BSON, and must still come back quoted. Where no type is declared, fall back to the value's shape and quote anything not plainly numeric or boolean — quoting is the safe direction, since an unquoted arbitrary string may not parse at all. **The round trip is the test, not the output**: describe → `check` → `exec` → describe must be byte-identical and leave `mx check` at 0 errors. Tests `mdl/executor/cmd_pages_describe_pluggable_roundtrip_test.go`; example `mdl-examples/bug-tests/pluggable-describe-roundtrip.mdl`, verified end to end on 11.12.1. **Uncovered while verifying, NOT fixed**: giving a property in a conditionally shown group a non-default value writes a widget Mendix rejects with CE0463 — ProgressCircle's `showLabel: true` and `labelType: 'percentage'` both do it with no DESCRIBE involved, while the same widget's General-group properties take non-default values happily. Reported in mxcli-ledger FINDINGS #104 | | `DESCRIBE PAGE` output will not re-parse for any page carrying a **pluggable widget**: `mxcli check` on it fails with `extraneous input ':'` / `extraneous input '('` from the first widget onward. Separately, a boolean property the author set is missing from the description entirely | Two independent defects in the same path. **Emit**: explicit properties were written with a raw `%s`, so every string lost its quotes — a JSON `spec: {"a": 1}` then broke the parse at its first brace. **Read**: `extractExplicitProperties` skipped any value of `"true"`/`"false"` as a "common default", so booleans never reached the output | `mdl/executor/cmd_pages_describe_output.go` (`explicitPropValue`, `isBareLiteral`), `mdl/executor/cmd_pages_describe_pluggable.go` (`buildPropertyValueTypeMap`, `extractExplicitProperties`), `mdl/executor/cmd_pages_describe.go` (`rawExplicitProp.ValueType`) | **Fixing one half alone is worse than the bug.** Quote without emitting booleans and the description re-parses cleanly while silently dropping a property — a wrong page that validates. Both halves ship together or neither. **Quote by the DECLARED type, never the value's shape**: `ValueType.Type` sits in the widget's `Type.ObjectType.PropertyTypes`, the same array `buildPropertyTypeKeyMap` already walks for `PropertyKey` and throws away; a String property holding `"30"` or `"true"` is indistinguishable from a number once it is a string in BSON, and must still come back quoted. Where no type is declared, fall back to the value's shape and quote anything not plainly numeric or boolean — quoting is the safe direction, since an unquoted arbitrary string may not parse at all. **The round trip is the test, not the output**: describe → `check` → `exec` → describe must be byte-identical and leave `mx check` at 0 errors. Tests `mdl/executor/cmd_pages_describe_pluggable_roundtrip_test.go`; example `mdl-examples/bug-tests/pluggable-describe-roundtrip.mdl`, verified end to end on 11.12.1. Reported in mxcli-ledger FINDINGS #104 | | Authoring a pluggable widget property that lives in a **conditionally shown group** writes a widget Mendix rejects with **CE0463**, while the same widget's other properties take non-default values happily. On ProgressCircle both `showLabel: true` and `labelType: 'percentage'` do it; `showLabel: false` and General-group properties are clean. No DESCRIBE involved | Two gaps on the same axis. **Serialization**: #574 nulls the TextTemplate of a HIDDEN conditional property, but left a VISIBLE one null — and Mendix stores an empty `Forms$ClientTemplate` there. Null and empty are each invalid in the other's state, so nulling hidden ones was only half the rule. **Extraction**: the editorConfig reader did not understand a ternary's ELSE branch (`cond ? (…) : hidePropertiesIn([…])`), so the `showLabel` gate was never seen and `labelText` read as visible whenever `labelType` was `"text"` — its default | `mdl/backend/widgetobj/builder.go` (`ApplyVisibilityRules`, `bsonFieldIsNil`), `mdl/executor/editorconfig_extract.go` (`parseGuard` `:` case, `ternaryCondition`, `trailingExpr`) | **Fixing one gap alone inverts the bug rather than closing it** — filling visible templates without the missing gate made `showLabel: false` fail where `true` had, because mxcli still thought labelText was visible. Measured both ways round before and after; a fix that moves which case fails is not a fix. **Let Mendix say what the shape should be**: `mx update-widgets` on a COPY of the failing project reconciles the widget, and diffing that against mxcli's output named the single meaningful path (`Object/Properties[N]/Value/TextTemplate` null vs `Forms$ClientTemplate`) out of 969. **The good/bad control does the isolation for free**: authoring the same widget with the boolean both ways gave two documents differing in exactly ONE path, so no other candidate needed testing. **Only CONDITIONAL properties are filled** — Studio Pro's convention for an unset TextTemplate is not uniform (a DataGrid custom-content column stores null for `tooltip` and an empty template for `exportValue`, per `emptyClientTemplateRules`), so filling every unset one would trade this bug for its mirror image. **The extractor's preamble matters**: the ternary is preceded by a whole `switch`, and walking back past the `?` to the function start yields a fragment with an unbalanced `}` that parses to nothing and looks like "unsupported shape" — hence `trailingExpr`. Regression signal: the widgetdemo showcase applies with **0 CE0463** (its 4 CE1613 are a pre-existing attribute reference). Tests `mdl/backend/widgetobj/widget_visibility_test.go`, `mdl/executor/editorconfig_extract_test.go`; example `mdl-examples/bug-tests/pluggable-describe-roundtrip.mdl` now exercises the group. Reported in mxcli-ledger FINDINGS #104 follow-on | +| `DROP FOLDER 'x' IN Module` reports success, then Studio Pro / `mx check` fails **CE1613** "The selected import mapping 'Module.X' no longer exists" for documents that are still in the project. `LIST FOLDERS` had shown that folder as `[0]` | Two halves of one blind spot. (a) `execDropFolder`'s doc comment claimed "the folder must be empty" but **nothing checked** — it called `DeleteFolder` unconditionally, and the children were left pointing at a container that no longer existed. Nothing is *deleted*: every document is **orphaned**, losing its module qualification (`FeedbackModule.IMM_PostResponse` → `.IMM_PostResponse`), so it survives as a unit that nothing can resolve. (b) `documentsByContainer` is a hand-maintained list of twelve document kinds; JSON structures, import/export mappings, regular expressions and image collections were missing, so the folder rendered `[0]` — which is what made the drop look safe | `mdl/executor/cmd_folders.go` (`execDropFolder`, `folderContentSummary`), `mdl/executor/cmd_list_folders.go` (`documentsByContainer`) | Guard on **`ctx.Backend.ListUnits()`**, never on the per-kind lists — `ListUnits` is type-agnostic, so it cannot inherit the blind spot that caused the bug, whereas a guard built on `documentsByContainer` would wave through exactly the kinds it forgets. Folders are units too (`Projects$Folder`), so one containment scan covers documents *and* sub-folders. **Fail closed**: if `ListUnits` errors, refuse — for a destructive op "I could not check" must never mean "go ahead". Note the mock's `ListUnits` defaults to `nil, nil` rather than an error, so a test that forgets to stub it sees an empty project and the guard silently passes; stub it explicitly. Separately add the missing kinds to `documentsByContainer` so the count stops lying. Repro `mdl-examples/bug-tests/892-drop-folder-not-empty.fail.mdl`. Issue #892 | diff --git a/CHANGELOG.md b/CHANGELOG.md index dde975e12..dfc00dc92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +### Fixed + +- **`DROP FOLDER` no longer orphans the documents inside it** (#892) — the command's contract ("the folder must be empty") existed only in a comment; nothing checked, so dropping a populated folder left every document pointing at a container that no longer existed. Nothing was deleted: the documents were **orphaned**, losing their module qualification (`FeedbackModule.IMM_PostResponse` → `.IMM_PostResponse`) so nothing could resolve them and mxbuild reported CE1613. Reproduced on a *stock* blank app, where `FeedbackModule/Private/Resources/Mappings` holds four documents. The drop is now refused, naming what is inside. The guard reads the type-agnostic unit list rather than the per-kind document lists, so it cannot inherit the blind spot that caused the bug, and it fails closed when contents cannot be determined. +- **`LIST FOLDERS` counts mappings, JSON structures, regular expressions and image collections** (#892) — these five kinds were missing from the per-kind listing, so a folder holding them rendered as `[0]`. That empty count is what made dropping the folder look safe. + ### Changed - **Go toolchain 1.26.5 → 1.26.6** for GO-2026-6218 (`net/url`), GO-2026-6090 (`crypto/tls`), GO-2026-6089 (`net/http`), GO-2026-6088 (`encoding/xml`), GO-2026-5972 (`encoding/asn1`) and GO-2026-5026 (`net/http`, via `golang.org/x/net/idna`). All six are standard-library advisories fixed in go1.26.6; no mxcli code changed. Bumped in `go.mod` and in all three workflows (`push-test`, `release`, `nightly`) together, so released binaries are not still linked against the vulnerable standard library. diff --git a/mdl-examples/bug-tests/892-drop-folder-not-empty.fail.mdl b/mdl-examples/bug-tests/892-drop-folder-not-empty.fail.mdl new file mode 100644 index 000000000..297fbea5e --- /dev/null +++ b/mdl-examples/bug-tests/892-drop-folder-not-empty.fail.mdl @@ -0,0 +1,30 @@ +-- upstream #892 — DROP FOLDER orphaned every document inside the folder. +-- +-- Reproduces on a STOCK blank app: FeedbackModule ships a folder +-- `Private/Resources/Mappings` holding four documents (two JSON structures, +-- one import mapping, one export mapping). +-- +-- Before the fix: +-- * `LIST FOLDERS` rendered that folder as `[0]` — documentsByContainer named +-- twelve document kinds and mappings/JSON structures were not among them. +-- * `DROP FOLDER` reported success. Its doc comment claimed "the folder must +-- be empty", but nothing checked. +-- * Nothing was deleted. Every document inside lost its module containment: +-- `FeedbackModule.IMM_PostResponse` became `.IMM_PostResponse`, so nothing +-- could resolve it and `mx check` reported: +-- [error] [CE1613] "The selected import mapping +-- 'FeedbackModule.IMM_PostResponse' no longer exists." +-- [error] [CE1613] "The selected export mapping +-- 'FeedbackModule.EXM_PostFeedback' no longer exists." +-- +-- After the fix the DROP is refused and the four documents are untouched. +-- This file is expected to FAIL — that refusal is the fix. +-- +-- mxcli exec 892-drop-folder-not-empty.fail.mdl -p .mpr +-- mx check .mpr # 0 errors, documents intact +-- +-- Run `list folders in FeedbackModule` first: the folder must report [4], not +-- [0]. A [0] there is the listing half of this bug and makes the drop below +-- look safe. + +drop folder 'Private/Resources/Mappings' in FeedbackModule; diff --git a/mdl/executor/cmd_folders.go b/mdl/executor/cmd_folders.go index 95dbe91a5..05c08403b 100644 --- a/mdl/executor/cmd_folders.go +++ b/mdl/executor/cmd_folders.go @@ -5,6 +5,7 @@ package executor import ( "fmt" + "sort" "strings" "github.com/mendixlabs/mxcli/mdl/ast" @@ -48,8 +49,72 @@ func findFolderByPath(ctx *ExecContext, moduleID model.ID, folderPath string, fo return targetFolderID, nil } +// folderContentSummary reports what sits directly inside a folder. +// +// It reads UnitInfo, which is type-agnostic, rather than the per-kind lists +// LIST FOLDERS renders from (documentsByContainer). That distinction is the +// whole fix for #892: documentsByContainer names twelve document kinds and +// JSON structures, mappings, message definitions and others are not among +// them, so the folder holding Mendix's own FeedbackModule mappings rendered as +// `[0]` while holding four documents. A guard built on the same list would +// inherit the same blind spot and still wave the drop through. +// +// Counts are rendered in a stable order so the refusal message does not vary +// between runs. +func folderContentSummary(folderID model.ID, units []*types.UnitInfo) (int, string) { + counts := map[string]int{} + total := 0 + for _, u := range units { + if u == nil || u.ContainerID != folderID { + continue + } + total++ + counts[folderContentKind(u.Type)]++ + } + if total == 0 { + return 0, "" + } + + kinds := make([]string, 0, len(counts)) + for k := range counts { + kinds = append(kinds, k) + } + sort.Strings(kinds) + + parts := make([]string, 0, len(kinds)) + for _, k := range kinds { + parts = append(parts, fmt.Sprintf("%d %s", counts[k], plural(counts[k], k, k+"s"))) + } + return total, strings.Join(parts, ", ") +} + +// folderContentKind turns a storage type into something worth reading in an +// error message: "JsonStructures$JsonStructure" -> "JsonStructure". +func folderContentKind(storageType string) string { + if storageType == folderStorageType { + return "sub-folder" + } + if _, after, ok := strings.Cut(storageType, "$"); ok && after != "" { + return after + } + if storageType == "" { + return "document" + } + return storageType +} + +// folderStorageType is the unit type of a folder itself; folders are units, so +// a sub-folder shows up in the same containment scan as a document. +const folderStorageType = "Projects$Folder" + // execDropFolder handles DROP FOLDER 'path' IN Module statements. -// The folder must be empty (no child documents or sub-folders). +// +// The folder must be empty (no child documents or sub-folders). Before #892 +// that contract existed only in this comment: nothing checked, and deleting a +// populated folder left every document inside it pointing at a container that +// no longer existed. They were not deleted — they were orphaned, losing their +// module qualification (`FeedbackModule.IMM_PostResponse` -> `.IMM_PostResponse`) +// so nothing could resolve them and mxbuild reported CE1613 "no longer exists". func execDropFolder(ctx *ExecContext, s *ast.DropFolderStmt) error { if !ctx.ConnectedForWrite() { return mdlerrors.NewNotConnected() @@ -70,6 +135,20 @@ func execDropFolder(ctx *ExecContext, s *ast.DropFolderStmt) error { return fmt.Errorf("%w in %s", err, s.Module) } + // Fail closed. For a destructive operation "I could not check" must never + // mean "go ahead" — that is exactly how #892 destroyed containment. + units, err := ctx.Backend.ListUnits() + if err != nil { + return mdlerrors.NewBackend(fmt.Sprintf("check whether folder '%s' is empty", s.FolderPath), err) + } + if n, summary := folderContentSummary(folderID, units); n > 0 { + return mdlerrors.NewValidationf( + "folder '%s' in %s is not empty: it holds %s. "+ + "Dropping it would leave those documents without a module, so nothing could resolve them "+ + "(mxbuild reports CE1613). Move or drop the contents first.", + s.FolderPath, s.Module, summary) + } + if err := ctx.Backend.DeleteFolder(folderID); err != nil { return mdlerrors.NewBackend(fmt.Sprintf("delete folder '%s'", s.FolderPath), err) } diff --git a/mdl/executor/cmd_folders_mock_test.go b/mdl/executor/cmd_folders_mock_test.go index c355ee29a..b7f2cc409 100644 --- a/mdl/executor/cmd_folders_mock_test.go +++ b/mdl/executor/cmd_folders_mock_test.go @@ -188,3 +188,96 @@ func TestMoveFolder_ToFolder(t *testing.T) { } assertContainsStr(t, buf.String(), "Moved folder") } + +// --------------------------------------------------------------------------- +// execDropFolder — emptiness guard (#892) +// +// The guard reads ListUnits, not the per-kind lists LIST FOLDERS renders from. +// That is the whole point: the hand-maintained list in documentsByContainer is +// what under-counted the folder to [0] and made the drop look safe, so a guard +// built on the same list would inherit the same blind spot. +// --------------------------------------------------------------------------- + +// dropFolderBackend wires a module, one folder, and whatever units the caller +// wants inside it. +func dropFolderBackend(mod *model.Module, folderID model.ID, units []*types.UnitInfo, deleted *bool) *mock.MockBackend { + return &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModulesFunc: func() ([]*model.Module, error) { return []*model.Module{mod}, nil }, + ListFoldersFunc: func() ([]*types.FolderInfo, error) { + return []*types.FolderInfo{{ID: folderID, ContainerID: mod.ID, Name: "Resources"}}, nil + }, + ListUnitsFunc: func() ([]*types.UnitInfo, error) { return units, nil }, + DeleteFolderFunc: func(id model.ID) error { *deleted = true; return nil }, + } +} + +// A JSON structure is exactly the case from #892: LIST FOLDERS cannot name it, +// so the folder rendered as [0] while holding a document. +func TestDropFolder_RefusesFolderHoldingDocumentOfAnUnlistedKind(t *testing.T) { + mod := mkModule("MyModule") + folderID := nextID("folder") + deleted := false + units := []*types.UnitInfo{ + {ID: nextID("unit"), ContainerID: folderID, Type: "JsonStructures$JsonStructure"}, + } + ctx, _ := newMockCtx(t, withBackend(dropFolderBackend(mod, folderID, units, &deleted)), withHierarchy(mkHierarchy(mod))) + + err := execDropFolder(ctx, &ast.DropFolderStmt{FolderPath: "Resources", Module: "MyModule"}) + assertError(t, err) + assertContainsStr(t, err.Error(), "not empty") + if deleted { + t.Error("DeleteFolder was called on a folder holding a document — this is the #892 data-loss path") + } +} + +func TestDropFolder_RefusesFolderWithSubfolder(t *testing.T) { + mod := mkModule("MyModule") + folderID := nextID("folder") + deleted := false + units := []*types.UnitInfo{ + {ID: nextID("unit"), ContainerID: folderID, Type: "Projects$Folder"}, + } + ctx, _ := newMockCtx(t, withBackend(dropFolderBackend(mod, folderID, units, &deleted)), withHierarchy(mkHierarchy(mod))) + + err := execDropFolder(ctx, &ast.DropFolderStmt{FolderPath: "Resources", Module: "MyModule"}) + assertError(t, err) + assertContainsStr(t, err.Error(), "not empty") + if deleted { + t.Error("DeleteFolder was called on a folder holding a sub-folder") + } +} + +// Units elsewhere in the project must not block the drop. +func TestDropFolder_AllowsEmptyFolderWithUnitsElsewhere(t *testing.T) { + mod := mkModule("MyModule") + folderID := nextID("folder") + deleted := false + units := []*types.UnitInfo{ + {ID: nextID("unit"), ContainerID: mod.ID, Type: "JsonStructures$JsonStructure"}, + {ID: folderID, ContainerID: mod.ID, Type: "Projects$Folder"}, + } + ctx, _ := newMockCtx(t, withBackend(dropFolderBackend(mod, folderID, units, &deleted)), withHierarchy(mkHierarchy(mod))) + + assertNoError(t, execDropFolder(ctx, &ast.DropFolderStmt{FolderPath: "Resources", Module: "MyModule"})) + if !deleted { + t.Error("an empty folder should still be droppable") + } +} + +// Fail closed. For a destructive op, "I could not check" must never mean "go +// ahead" — that is precisely how #892 destroyed containment. +func TestDropFolder_RefusesWhenContentsCannotBeDetermined(t *testing.T) { + mod := mkModule("MyModule") + folderID := nextID("folder") + deleted := false + mb := dropFolderBackend(mod, folderID, nil, &deleted) + mb.ListUnitsFunc = func() ([]*types.UnitInfo, error) { return nil, fmt.Errorf("backend says no") } + ctx, _ := newMockCtx(t, withBackend(mb), withHierarchy(mkHierarchy(mod))) + + err := execDropFolder(ctx, &ast.DropFolderStmt{FolderPath: "Resources", Module: "MyModule"}) + assertError(t, err) + if deleted { + t.Error("DeleteFolder was called without being able to confirm the folder was empty") + } +} diff --git a/mdl/executor/cmd_list_folders.go b/mdl/executor/cmd_list_folders.go index abdd99915..5aa0568a4 100644 --- a/mdl/executor/cmd_list_folders.go +++ b/mdl/executor/cmd_list_folders.go @@ -229,6 +229,34 @@ func documentsByContainer(ctx *ExecContext, h *ContainerHierarchy) map[model.ID] put("ScheduledEvent", x.Name, x.ContainerID) } } + // #892: these five were missing, which is why the folder holding Mendix's + // own FeedbackModule mappings rendered as `[0]` while holding four + // documents — and an empty count is what made dropping it look safe. + if v, err := ctx.Backend.ListJsonStructures(); err == nil { + for _, x := range v { + put("JsonStructure", x.Name, x.ContainerID) + } + } + if v, err := ctx.Backend.ListImportMappings(); err == nil { + for _, x := range v { + put("ImportMapping", x.Name, x.ContainerID) + } + } + if v, err := ctx.Backend.ListExportMappings(); err == nil { + for _, x := range v { + put("ExportMapping", x.Name, x.ContainerID) + } + } + if v, err := ctx.Backend.ListRegularExpressions(); err == nil { + for _, x := range v { + put("RegularExpression", x.Name, x.ContainerID) + } + } + if v, err := ctx.Backend.ListImageCollections(); err == nil { + for _, x := range v { + put("ImageCollection", x.Name, x.ContainerID) + } + } return out } diff --git a/mdl/executor/cmd_list_folders_test.go b/mdl/executor/cmd_list_folders_test.go index 38f7a5adb..9f9f2d6fc 100644 --- a/mdl/executor/cmd_list_folders_test.go +++ b/mdl/executor/cmd_list_folders_test.go @@ -108,3 +108,41 @@ func foldersFixture(t *testing.T) (*ExecContext, *bytes.Buffer) { ctx, buf := newMockCtx(t, withBackend(mb)) return ctx, buf } + +// #892: the folder holding Mendix's own FeedbackModule mappings rendered as +// `[0]` because documentsByContainer named twelve document kinds and mappings +// and JSON structures were not among them. That empty count is what made +// dropping the folder look safe. +func TestListFolders_CountsMappingsAndJsonStructures(t *testing.T) { + mod := mkModule("Fb") + mappings := &types.FolderInfo{ID: "f-map", ContainerID: mod.ID, Name: "Mappings"} + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModulesFunc: func() ([]*model.Module, error) { return []*model.Module{mod}, nil }, + ListFoldersFunc: func() ([]*types.FolderInfo, error) { + return []*types.FolderInfo{mappings}, nil + }, + ListUnitsFunc: func() ([]*types.UnitInfo, error) { return nil, nil }, + ListJsonStructuresFunc: func() ([]*types.JsonStructure, error) { + return []*types.JsonStructure{{Name: "JSON_Response", ContainerID: mappings.ID}}, nil + }, + ListImportMappingsFunc: func() ([]*model.ImportMapping, error) { + return []*model.ImportMapping{{Name: "IMM_PostResponse", ContainerID: mappings.ID}}, nil + }, + ListExportMappingsFunc: func() ([]*model.ExportMapping, error) { + return []*model.ExportMapping{{Name: "EXM_PostFeedback", ContainerID: mappings.ID}}, nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb)) + assertNoError(t, listFolders(ctx, &ast.ShowStmt{ObjectType: ast.ShowFolders, InModule: "Fb"})) + out := buf.String() + + if strings.Contains(out, "Mappings [0]") { + t.Errorf("folder still reports [0] while holding three documents — this is the count that made #892 look safe:\n%s", out) + } + assertContainsStr(t, out, "JsonStructure JSON_Response") + assertContainsStr(t, out, "ImportMapping IMM_PostResponse") + assertContainsStr(t, out, "ExportMapping EXM_PostFeedback") +} From 64b110e90decfc27f832d8bff1c1bd42b25ad4e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 18:35:52 +0000 Subject: [PATCH 02/22] fix(alter-page): refuse a bare DataGrid2 column as an INSERT/REPLACE target (#891) `REPLACE NextRunAt WITH { COLUMN ... }` and `INSERT AFTER PageSize { COLUMN ... }` reported "Altered page" and left a project mxbuild could not even LOAD: System.InvalidCastException: Unable to cast object of type '...LayoutWidgets.DivContainers.DivContainer' to type '...CustomWidgets.WidgetObject' ast.WidgetRef treats only a DOTTED name as a column, so a bare `NextRunAt` gives Widget="NextRunAt", Column="", IsColumn()==false. The op skipped the InsertColumns/ReplaceColumn paths -- which work -- and took the generic widget path, which built the COLUMN as a layout container and wrote it into the grid's column list. Nothing refused because findBsonWidget recurses into pluggable-widget internals, so the bare name DID resolve, to the column node. Three corrections to the report. The issue calls REPLACE "deletes the column without writing the replacement" and INSERT "reports success but makes no changes"; both are corruption, and DESCRIBE PAGE only made them look benign by skipping the malformed node. And neither needs the grid nested in a pluggable widget -- both reproduce on a plain top-level DataGrid2 in a blank app. Discriminates on the resolved node's $Type: an object-list item (DataGrid2 column, Accordion group, PopupMenu basicItem) is CustomWidgets$WidgetObject; a real widget is Forms$* or CustomWidgets$CustomWidget. Not on a name-match count -- columnMatchCount already existed but refused only when n > 1, so the single-match case sailed through, and a widget sharing a name with some column elsewhere would be a false positive. Refuses rather than resolving: guessing which grid was meant is what produced the invalid document. The message names the qualified `grid.column` form, which always worked and is unaffected. The guard lives in the mutator, not the executor. An executor-level check on FindWidget only catches names that are absent entirely -- which the mutator already refuses -- because FindWidget matches columns too. Verified on a blank 11.13 app across all four forms: both bare forms now refuse with the project left at 0 errors (previously unloadable), both qualified forms still apply and check clean. Control run with the guard stubbed reproduces the InvalidCastException. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013uQvFDd5R4eNqqita59jM8 --- .claude/skills/fix-issue.md | 1 + CHANGELOG.md | 2 + ...891-alter-page-bare-column-target.fail.mdl | 48 +++++++++++++++ mdl/backend/pagemutator/mutator.go | 41 +++++++++++++ .../mutator_column_addressing_test.go | 60 +++++++++++++++++++ 5 files changed, 152 insertions(+) create mode 100644 mdl-examples/bug-tests/891-alter-page-bare-column-target.fail.mdl diff --git a/.claude/skills/fix-issue.md b/.claude/skills/fix-issue.md index 96afd0355..3ba73dd77 100644 --- a/.claude/skills/fix-issue.md +++ b/.claude/skills/fix-issue.md @@ -516,3 +516,4 @@ extracting `OffsetExpression`/`LimitExpression`. | `DESCRIBE PAGE` output will not re-parse for any page carrying a **pluggable widget**: `mxcli check` on it fails with `extraneous input ':'` / `extraneous input '('` from the first widget onward. Separately, a boolean property the author set is missing from the description entirely | Two independent defects in the same path. **Emit**: explicit properties were written with a raw `%s`, so every string lost its quotes — a JSON `spec: {"a": 1}` then broke the parse at its first brace. **Read**: `extractExplicitProperties` skipped any value of `"true"`/`"false"` as a "common default", so booleans never reached the output | `mdl/executor/cmd_pages_describe_output.go` (`explicitPropValue`, `isBareLiteral`), `mdl/executor/cmd_pages_describe_pluggable.go` (`buildPropertyValueTypeMap`, `extractExplicitProperties`), `mdl/executor/cmd_pages_describe.go` (`rawExplicitProp.ValueType`) | **Fixing one half alone is worse than the bug.** Quote without emitting booleans and the description re-parses cleanly while silently dropping a property — a wrong page that validates. Both halves ship together or neither. **Quote by the DECLARED type, never the value's shape**: `ValueType.Type` sits in the widget's `Type.ObjectType.PropertyTypes`, the same array `buildPropertyTypeKeyMap` already walks for `PropertyKey` and throws away; a String property holding `"30"` or `"true"` is indistinguishable from a number once it is a string in BSON, and must still come back quoted. Where no type is declared, fall back to the value's shape and quote anything not plainly numeric or boolean — quoting is the safe direction, since an unquoted arbitrary string may not parse at all. **The round trip is the test, not the output**: describe → `check` → `exec` → describe must be byte-identical and leave `mx check` at 0 errors. Tests `mdl/executor/cmd_pages_describe_pluggable_roundtrip_test.go`; example `mdl-examples/bug-tests/pluggable-describe-roundtrip.mdl`, verified end to end on 11.12.1. Reported in mxcli-ledger FINDINGS #104 | | Authoring a pluggable widget property that lives in a **conditionally shown group** writes a widget Mendix rejects with **CE0463**, while the same widget's other properties take non-default values happily. On ProgressCircle both `showLabel: true` and `labelType: 'percentage'` do it; `showLabel: false` and General-group properties are clean. No DESCRIBE involved | Two gaps on the same axis. **Serialization**: #574 nulls the TextTemplate of a HIDDEN conditional property, but left a VISIBLE one null — and Mendix stores an empty `Forms$ClientTemplate` there. Null and empty are each invalid in the other's state, so nulling hidden ones was only half the rule. **Extraction**: the editorConfig reader did not understand a ternary's ELSE branch (`cond ? (…) : hidePropertiesIn([…])`), so the `showLabel` gate was never seen and `labelText` read as visible whenever `labelType` was `"text"` — its default | `mdl/backend/widgetobj/builder.go` (`ApplyVisibilityRules`, `bsonFieldIsNil`), `mdl/executor/editorconfig_extract.go` (`parseGuard` `:` case, `ternaryCondition`, `trailingExpr`) | **Fixing one gap alone inverts the bug rather than closing it** — filling visible templates without the missing gate made `showLabel: false` fail where `true` had, because mxcli still thought labelText was visible. Measured both ways round before and after; a fix that moves which case fails is not a fix. **Let Mendix say what the shape should be**: `mx update-widgets` on a COPY of the failing project reconciles the widget, and diffing that against mxcli's output named the single meaningful path (`Object/Properties[N]/Value/TextTemplate` null vs `Forms$ClientTemplate`) out of 969. **The good/bad control does the isolation for free**: authoring the same widget with the boolean both ways gave two documents differing in exactly ONE path, so no other candidate needed testing. **Only CONDITIONAL properties are filled** — Studio Pro's convention for an unset TextTemplate is not uniform (a DataGrid custom-content column stores null for `tooltip` and an empty template for `exportValue`, per `emptyClientTemplateRules`), so filling every unset one would trade this bug for its mirror image. **The extractor's preamble matters**: the ternary is preceded by a whole `switch`, and walking back past the `?` to the function start yields a fragment with an unbalanced `}` that parses to nothing and looks like "unsupported shape" — hence `trailingExpr`. Regression signal: the widgetdemo showcase applies with **0 CE0463** (its 4 CE1613 are a pre-existing attribute reference). Tests `mdl/backend/widgetobj/widget_visibility_test.go`, `mdl/executor/editorconfig_extract_test.go`; example `mdl-examples/bug-tests/pluggable-describe-roundtrip.mdl` now exercises the group. Reported in mxcli-ledger FINDINGS #104 follow-on | | `DROP FOLDER 'x' IN Module` reports success, then Studio Pro / `mx check` fails **CE1613** "The selected import mapping 'Module.X' no longer exists" for documents that are still in the project. `LIST FOLDERS` had shown that folder as `[0]` | Two halves of one blind spot. (a) `execDropFolder`'s doc comment claimed "the folder must be empty" but **nothing checked** — it called `DeleteFolder` unconditionally, and the children were left pointing at a container that no longer existed. Nothing is *deleted*: every document is **orphaned**, losing its module qualification (`FeedbackModule.IMM_PostResponse` → `.IMM_PostResponse`), so it survives as a unit that nothing can resolve. (b) `documentsByContainer` is a hand-maintained list of twelve document kinds; JSON structures, import/export mappings, regular expressions and image collections were missing, so the folder rendered `[0]` — which is what made the drop look safe | `mdl/executor/cmd_folders.go` (`execDropFolder`, `folderContentSummary`), `mdl/executor/cmd_list_folders.go` (`documentsByContainer`) | Guard on **`ctx.Backend.ListUnits()`**, never on the per-kind lists — `ListUnits` is type-agnostic, so it cannot inherit the blind spot that caused the bug, whereas a guard built on `documentsByContainer` would wave through exactly the kinds it forgets. Folders are units too (`Projects$Folder`), so one containment scan covers documents *and* sub-folders. **Fail closed**: if `ListUnits` errors, refuse — for a destructive op "I could not check" must never mean "go ahead". Note the mock's `ListUnits` defaults to `nil, nil` rather than an error, so a test that forgets to stub it sees an empty project and the guard silently passes; stub it explicitly. Separately add the missing kinds to `documentsByContainer` so the count stops lying. Repro `mdl-examples/bug-tests/892-drop-folder-not-empty.fail.mdl`. Issue #892 | +| `ALTER PAGE` **`REPLACE `** or **`INSERT AFTER `** naming a DataGrid2 column with a BARE name reports "Altered page", but the column vanishes (REPLACE) or nothing changes (INSERT) — and Studio Pro / `mx check` then cannot **LOAD** the project: `System.InvalidCastException: Unable to cast object of type '…LayoutWidgets.DivContainers.DivContainer' to type '…CustomWidgets.WidgetObject'` | `ast.WidgetRef` treats only a **dotted** name as a column, so a bare `NextRunAt` gives `Widget="NextRunAt"`, `Column=""`, `IsColumn()==false`. The op skipped the `InsertColumns`/`ReplaceColumn` paths — which work correctly — and fell through to the generic widget path, which built the `COLUMN` as a **layout container** and wrote it into the grid's column list. Nothing refused because `findBsonWidget` **recurses into pluggable-widget internals**, so the bare name *did* resolve — to the column node. `DESCRIBE PAGE` then skipped the malformed node, which is what made REPLACE look like a clean deletion and INSERT like a harmless no-op | `mdl/backend/pagemutator/mutator.go` (`refuseObjectListItemTarget`, called from `InsertWidget` + `ReplaceWidget`) | Discriminate on the resolved node's **`$Type`**: an object-list item (DataGrid2 column, Accordion group, PopupMenu basicItem) is `CustomWidgets$WidgetObject`, a real widget is `Forms$*` / `CustomWidgets$CustomWidget`. Refuse the bare form and name the qualified `grid.column` in the message — do **not** auto-resolve, since guessing which grid was meant is what produced the invalid document. Do not discriminate by *counting* name matches: `columnMatchCount` already existed but only refused when `n > 1`, so the single-match case (the common one) sailed through, and a widget that merely shares a name with some column elsewhere would be a false positive. Guard belongs in the **mutator**, not the executor — `FindWidget` matches columns too, so an executor-level check on it only catches names that are absent entirely, which the mutator already refuses. Repro `mdl-examples/bug-tests/891-alter-page-bare-column-target.fail.mdl`. Issue #891 | diff --git a/CHANGELOG.md b/CHANGELOG.md index dfc00dc92..157f75c39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Fixed +- **`ALTER PAGE INSERT`/`REPLACE` no longer corrupts a page when a DataGrid2 column is named without its grid** (#891) — `REPLACE NextRunAt WITH { COLUMN … }` reported success while writing a layout container into the grid's column list, leaving a project Studio Pro and mxbuild could not **load** (`InvalidCastException: DivContainer → WidgetObject`). `DESCRIBE PAGE` skipped the malformed node, so REPLACE looked like a clean deletion and INSERT like a harmless no-op; both were corruption, and neither required the grid to be nested in a pluggable widget. A bare name that resolves to an object-list item is now refused, naming the qualified `grid.column` form — which always worked and is unaffected. + - **`DROP FOLDER` no longer orphans the documents inside it** (#892) — the command's contract ("the folder must be empty") existed only in a comment; nothing checked, so dropping a populated folder left every document pointing at a container that no longer existed. Nothing was deleted: the documents were **orphaned**, losing their module qualification (`FeedbackModule.IMM_PostResponse` → `.IMM_PostResponse`) so nothing could resolve them and mxbuild reported CE1613. Reproduced on a *stock* blank app, where `FeedbackModule/Private/Resources/Mappings` holds four documents. The drop is now refused, naming what is inside. The guard reads the type-agnostic unit list rather than the per-kind document lists, so it cannot inherit the blind spot that caused the bug, and it fails closed when contents cannot be determined. - **`LIST FOLDERS` counts mappings, JSON structures, regular expressions and image collections** (#892) — these five kinds were missing from the per-kind listing, so a folder holding them rendered as `[0]`. That empty count is what made dropping the folder look safe. diff --git a/mdl-examples/bug-tests/891-alter-page-bare-column-target.fail.mdl b/mdl-examples/bug-tests/891-alter-page-bare-column-target.fail.mdl new file mode 100644 index 000000000..345c614f4 --- /dev/null +++ b/mdl-examples/bug-tests/891-alter-page-bare-column-target.fail.mdl @@ -0,0 +1,48 @@ +-- upstream #891 (3a/3b) — ALTER PAGE INSERT/REPLACE with a BARE DataGrid2 +-- column name corrupted the page. +-- +-- Setup (a plain TOP-LEVEL DataGrid2 — nesting in a pluggable widget is NOT +-- required, contrary to the issue's framing): +-- +-- create entity P91.SyncConfig ( ModelName: String(100), RecordCount: Integer, +-- PageSize: Integer, NextRunAt: DateTime ); +-- create page P91.SyncConfiguration_Overview +-- ( Title: 'Sync config', Layout: Atlas_Core.Atlas_Default ) +-- { DATAGRID grid1 (DataSource: DATABASE P91.SyncConfig) { +-- COLUMN ModelName (Attribute: ModelName, Caption: 'Model') +-- COLUMN RecordCount (Attribute: RecordCount, Caption: 'Records') +-- COLUMN PageSize (Attribute: PageSize, Caption: 'Page size') +-- COLUMN NextRunAt (Attribute: NextRunAt, Caption: 'Next run') +-- } } +-- +-- Before the fix BOTH statements below reported "Altered page" and left a +-- project mxbuild could not even LOAD: +-- +-- ERROR: System.InvalidCastException: Unable to cast object of type +-- 'Mendix.Modeler.WebUI.Forms.Widgets.LayoutWidgets.DivContainers.DivContainer' +-- to type 'Mendix.Modeler.WebUI.Forms.Widgets.CustomWidgets.WidgetObject' +-- +-- Cause: WidgetRef treats only a DOTTED name as a column, so a bare `NextRunAt` +-- gives Widget="NextRunAt", Column="" and IsColumn() == false. The op skipped +-- the InsertColumns/ReplaceColumn paths (which work) and took the generic +-- widget path, which built the COLUMN as a layout container and wrote it into +-- the grid's column list. findBsonWidget recurses into pluggable internals, so +-- the bare name DID resolve — to the column — which is why nothing refused. +-- +-- DESCRIBE PAGE masked it by skipping the malformed node: REPLACE looked like a +-- clean deletion, INSERT looked like a harmless no-op. Neither was. +-- +-- This file is expected to FAIL — the refusal is the fix. The qualified forms +-- `grid1.NextRunAt` / `grid1.PageSize` work and are what to use instead. + +ALTER PAGE P91.SyncConfiguration_Overview { + REPLACE NextRunAt WITH { + COLUMN NextRunAt (Attribute: NextRunAt, Caption: 'Next run') + }; +}; + +ALTER PAGE P91.SyncConfiguration_Overview { + INSERT AFTER PageSize { + COLUMN NewCol (Attribute: RecordCount, Caption: 'Inserted') + }; +}; diff --git a/mdl/backend/pagemutator/mutator.go b/mdl/backend/pagemutator/mutator.go index a55f83df0..b5591383d 100644 --- a/mdl/backend/pagemutator/mutator.go +++ b/mdl/backend/pagemutator/mutator.go @@ -219,6 +219,41 @@ func (m *Mutator) findStyleableWidget(widgetRef string) (bson.D, error) { return result.widget, nil } +// objectListItemType is the $Type of an object-list item — a DataGrid2 column, +// an Accordion group, a PopupMenu basicItem. These live inside a pluggable +// widget's property tree, not in a Widgets array, and the generic widget +// insert/replace path cannot write one. +const objectListItemType = "CustomWidgets$WidgetObject" + +// refuseObjectListItemTarget refuses an INSERT/REPLACE whose bare target resolved +// to an object-list item rather than a widget (#891). +// +// findBsonWidget recurses into a pluggable widget's internals, so a bare +// `NextRunAt` DOES resolve — to the grid column of that name. The op then took +// the generic widget path, which built the replacement as a layout container and +// wrote it into the grid's column list. Both INSERT and REPLACE reported success +// while leaving a project mxbuild could not even load: +// +// System.InvalidCastException: Unable to cast object of type +// '...LayoutWidgets.DivContainers.DivContainer' to type '...CustomWidgets.WidgetObject' +// +// DESCRIBE PAGE skipped the malformed node, which is why this looked like a +// clean deletion (REPLACE) or a no-op (INSERT). +// +// The dotted form `grid.column` routes to ReplaceColumn/InsertColumns and works, +// so this refuses and names that form rather than guessing which grid was meant. +// Guessing is what produced the invalid document. +func refuseObjectListItemTarget(result *bsonWidgetResult, name string) error { + if result == nil || bsonnav.DGetString(result.widget, "$Type") != objectListItemType { + return nil + } + return fmt.Errorf( + "%q is a DataGrid2 column, not a widget — qualify it as `gridName.%s` so the column "+ + "path is used. Addressing it bare writes into the grid's column list as a layout "+ + "container, leaving a project Studio Pro cannot open", + name, name) +} + func (m *Mutator) InsertWidget(widgetRef string, columnRef string, position backend.InsertPosition, widgets []pages.Widget) error { var result *bsonWidgetResult if columnRef != "" { @@ -232,6 +267,9 @@ func (m *Mutator) InsertWidget(widgetRef string, columnRef string, position back if result == nil { return m.widgetNotFoundError(widgetRef) } + if err := refuseObjectListItemTarget(result, widgetRef); err != nil { + return err + } if n := m.columnMatchCount(widgetRef); n > 1 { return columnAmbiguityError(widgetRef, n) } @@ -384,6 +422,9 @@ func (m *Mutator) ReplaceWidget(widgetRef string, columnRef string, widgets []pa if result == nil { return m.widgetNotFoundError(widgetRef) } + if err := refuseObjectListItemTarget(result, widgetRef); err != nil { + return err + } if n := m.columnMatchCount(widgetRef); n > 1 { return columnAmbiguityError(widgetRef, n) } diff --git a/mdl/backend/pagemutator/mutator_column_addressing_test.go b/mdl/backend/pagemutator/mutator_column_addressing_test.go index 599b5f018..05df392a4 100644 --- a/mdl/backend/pagemutator/mutator_column_addressing_test.go +++ b/mdl/backend/pagemutator/mutator_column_addressing_test.go @@ -180,3 +180,63 @@ func TestColumnMatchCount_CrossGrid(t *testing.T) { t.Errorf("missing name: count = %d, want 0", n) } } + +// --------------------------------------------------------------------------- +// #891: a bare DataGrid2 column name resolves through findBsonWidget, so +// INSERT/REPLACE took the generic widget path and wrote a layout container into +// the grid's column list — a project mxbuild could not load. Refuse instead. +// +// These go through ReplaceWidget/InsertWidget rather than calling the helper +// directly, so deleting either call site fails the test. A direct-call test +// would prove the helper works and nothing about the wiring (#884). +// --------------------------------------------------------------------------- + +func columnNodeFinder(t *testing.T) widgetFinder { + t.Helper() + col := bson.D{ + {Key: "$Type", Value: objectListItemType}, + {Key: "Name", Value: "NextRunAt"}, + } + return func(_ bson.D, name string) *bsonWidgetResult { + if name == "NextRunAt" { + return &bsonWidgetResult{widget: col} + } + return nil + } +} + +func TestReplaceWidget_RefusesBareColumnTarget(t *testing.T) { + m := &Mutator{rawData: bson.D{}, widgetFinder: columnNodeFinder(t)} + err := m.ReplaceWidget("NextRunAt", "", nil) + if err == nil { + t.Fatal("ReplaceWidget accepted a bare column target — this is the #891 corruption path") + } + if !strings.Contains(err.Error(), "gridName.NextRunAt") { + t.Errorf("error should name the qualified form, got: %v", err) + } +} + +func TestInsertWidget_RefusesBareColumnTarget(t *testing.T) { + m := &Mutator{rawData: bson.D{}, widgetFinder: columnNodeFinder(t)} + err := m.InsertWidget("NextRunAt", "", "AFTER", nil) + if err == nil { + t.Fatal("InsertWidget accepted a bare column target — this is the #891 corruption path") + } + if !strings.Contains(err.Error(), "gridName.NextRunAt") { + t.Errorf("error should name the qualified form, got: %v", err) + } +} + +// A real widget must not trip the guard, or every ordinary REPLACE would break. +// The two tests above already prove the call sites are wired; this pins the +// discriminator itself. +func TestObjectListItemGuard_IgnoresRealWidgets(t *testing.T) { + w := &bsonWidgetResult{widget: bson.D{{Key: "$Type", Value: "Forms$TextBox"}, {Key: "Name", Value: "txtName"}}} + if err := refuseObjectListItemTarget(w, "txtName"); err != nil { + t.Fatalf("guard wrongly refused a real widget: %v", err) + } + pluggable := &bsonWidgetResult{widget: bson.D{{Key: "$Type", Value: "CustomWidgets$CustomWidget"}, {Key: "Name", Value: "grid1"}}} + if err := refuseObjectListItemTarget(pluggable, "grid1"); err != nil { + t.Fatalf("guard wrongly refused a pluggable widget: %v", err) + } +} From b065b551a67d3c42c001d61c86aa907460e86d2c Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 20:20:15 +0000 Subject: [PATCH 03/22] fix(describe): emit widgets nested in a pluggable widget's object-list item (#891) DESCRIBE PAGE rendered an Accordion group with its own properties and nothing else, so a page whose group held a DataGrid2 described as: pluggablewidget '...accordion.Accordion' acc1 (...) { group group1 (HeaderRenderMode: 'text', ...) } The grid was genuinely in the model, so feeding that description back through exec silently deleted it -- the round-trip loss the reporter flagged, not merely a display gap. Two halves, and fixing either alone still prints an empty group. An object-list item can carry child widgets in a Widgets-typed sub-property (a group's `content` slot), but extractObjectListItem read only scalar sub-properties -- datasource, attribute, expression, text template, primitive -- and fell through on anything else. And the emitter always closed an item with "\n", so children had nowhere to go even once read. Reads the Widgets array with parseRawWidget, the same recursion the rest of DESCRIBE uses, and emits the item with a body through outputWidgetMDLV3 so nesting and indentation stay consistent. The keep-this-item test now also counts children, or a group whose only content is widgets is dropped wholesale. Generic, not Accordion-specific: any pluggable widget's object-list items (PopupMenu basicItems, and so on) get the same treatment. Verified on a stock blank app -- the Accordion widget ships in every Mendix project, so no marketplace install is involved. The nested grid and both columns now appear, the description re-parses, and re-executing it preserves the grid with the project's error count unchanged (2 before, 2 after; the CE0463 on the accordion comes from authoring one through the generic PLUGGABLEWIDGET path and is present with or without this change). Control run with the extraction stubbed drops the grid from the output again. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013uQvFDd5R4eNqqita59jM8 --- .claude/skills/fix-issue.md | 1 + CHANGELOG.md | 2 + .../891-accordion-group-nested-widgets.mdl | 53 +++++++ mdl/executor/cmd_pages_describe_objectlist.go | 20 ++- ...pages_describe_objectlist_children_test.go | 150 ++++++++++++++++++ mdl/executor/cmd_pages_describe_output.go | 11 ++ 6 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 mdl-examples/bug-tests/891-accordion-group-nested-widgets.mdl create mode 100644 mdl/executor/cmd_pages_describe_objectlist_children_test.go diff --git a/.claude/skills/fix-issue.md b/.claude/skills/fix-issue.md index 3ba73dd77..e5357b516 100644 --- a/.claude/skills/fix-issue.md +++ b/.claude/skills/fix-issue.md @@ -517,3 +517,4 @@ extracting `OffsetExpression`/`LimitExpression`. | Authoring a pluggable widget property that lives in a **conditionally shown group** writes a widget Mendix rejects with **CE0463**, while the same widget's other properties take non-default values happily. On ProgressCircle both `showLabel: true` and `labelType: 'percentage'` do it; `showLabel: false` and General-group properties are clean. No DESCRIBE involved | Two gaps on the same axis. **Serialization**: #574 nulls the TextTemplate of a HIDDEN conditional property, but left a VISIBLE one null — and Mendix stores an empty `Forms$ClientTemplate` there. Null and empty are each invalid in the other's state, so nulling hidden ones was only half the rule. **Extraction**: the editorConfig reader did not understand a ternary's ELSE branch (`cond ? (…) : hidePropertiesIn([…])`), so the `showLabel` gate was never seen and `labelText` read as visible whenever `labelType` was `"text"` — its default | `mdl/backend/widgetobj/builder.go` (`ApplyVisibilityRules`, `bsonFieldIsNil`), `mdl/executor/editorconfig_extract.go` (`parseGuard` `:` case, `ternaryCondition`, `trailingExpr`) | **Fixing one gap alone inverts the bug rather than closing it** — filling visible templates without the missing gate made `showLabel: false` fail where `true` had, because mxcli still thought labelText was visible. Measured both ways round before and after; a fix that moves which case fails is not a fix. **Let Mendix say what the shape should be**: `mx update-widgets` on a COPY of the failing project reconciles the widget, and diffing that against mxcli's output named the single meaningful path (`Object/Properties[N]/Value/TextTemplate` null vs `Forms$ClientTemplate`) out of 969. **The good/bad control does the isolation for free**: authoring the same widget with the boolean both ways gave two documents differing in exactly ONE path, so no other candidate needed testing. **Only CONDITIONAL properties are filled** — Studio Pro's convention for an unset TextTemplate is not uniform (a DataGrid custom-content column stores null for `tooltip` and an empty template for `exportValue`, per `emptyClientTemplateRules`), so filling every unset one would trade this bug for its mirror image. **The extractor's preamble matters**: the ternary is preceded by a whole `switch`, and walking back past the `?` to the function start yields a fragment with an unbalanced `}` that parses to nothing and looks like "unsupported shape" — hence `trailingExpr`. Regression signal: the widgetdemo showcase applies with **0 CE0463** (its 4 CE1613 are a pre-existing attribute reference). Tests `mdl/backend/widgetobj/widget_visibility_test.go`, `mdl/executor/editorconfig_extract_test.go`; example `mdl-examples/bug-tests/pluggable-describe-roundtrip.mdl` now exercises the group. Reported in mxcli-ledger FINDINGS #104 follow-on | | `DROP FOLDER 'x' IN Module` reports success, then Studio Pro / `mx check` fails **CE1613** "The selected import mapping 'Module.X' no longer exists" for documents that are still in the project. `LIST FOLDERS` had shown that folder as `[0]` | Two halves of one blind spot. (a) `execDropFolder`'s doc comment claimed "the folder must be empty" but **nothing checked** — it called `DeleteFolder` unconditionally, and the children were left pointing at a container that no longer existed. Nothing is *deleted*: every document is **orphaned**, losing its module qualification (`FeedbackModule.IMM_PostResponse` → `.IMM_PostResponse`), so it survives as a unit that nothing can resolve. (b) `documentsByContainer` is a hand-maintained list of twelve document kinds; JSON structures, import/export mappings, regular expressions and image collections were missing, so the folder rendered `[0]` — which is what made the drop look safe | `mdl/executor/cmd_folders.go` (`execDropFolder`, `folderContentSummary`), `mdl/executor/cmd_list_folders.go` (`documentsByContainer`) | Guard on **`ctx.Backend.ListUnits()`**, never on the per-kind lists — `ListUnits` is type-agnostic, so it cannot inherit the blind spot that caused the bug, whereas a guard built on `documentsByContainer` would wave through exactly the kinds it forgets. Folders are units too (`Projects$Folder`), so one containment scan covers documents *and* sub-folders. **Fail closed**: if `ListUnits` errors, refuse — for a destructive op "I could not check" must never mean "go ahead". Note the mock's `ListUnits` defaults to `nil, nil` rather than an error, so a test that forgets to stub it sees an empty project and the guard silently passes; stub it explicitly. Separately add the missing kinds to `documentsByContainer` so the count stops lying. Repro `mdl-examples/bug-tests/892-drop-folder-not-empty.fail.mdl`. Issue #892 | | `ALTER PAGE` **`REPLACE `** or **`INSERT AFTER `** naming a DataGrid2 column with a BARE name reports "Altered page", but the column vanishes (REPLACE) or nothing changes (INSERT) — and Studio Pro / `mx check` then cannot **LOAD** the project: `System.InvalidCastException: Unable to cast object of type '…LayoutWidgets.DivContainers.DivContainer' to type '…CustomWidgets.WidgetObject'` | `ast.WidgetRef` treats only a **dotted** name as a column, so a bare `NextRunAt` gives `Widget="NextRunAt"`, `Column=""`, `IsColumn()==false`. The op skipped the `InsertColumns`/`ReplaceColumn` paths — which work correctly — and fell through to the generic widget path, which built the `COLUMN` as a **layout container** and wrote it into the grid's column list. Nothing refused because `findBsonWidget` **recurses into pluggable-widget internals**, so the bare name *did* resolve — to the column node. `DESCRIBE PAGE` then skipped the malformed node, which is what made REPLACE look like a clean deletion and INSERT like a harmless no-op | `mdl/backend/pagemutator/mutator.go` (`refuseObjectListItemTarget`, called from `InsertWidget` + `ReplaceWidget`) | Discriminate on the resolved node's **`$Type`**: an object-list item (DataGrid2 column, Accordion group, PopupMenu basicItem) is `CustomWidgets$WidgetObject`, a real widget is `Forms$*` / `CustomWidgets$CustomWidget`. Refuse the bare form and name the qualified `grid.column` in the message — do **not** auto-resolve, since guessing which grid was meant is what produced the invalid document. Do not discriminate by *counting* name matches: `columnMatchCount` already existed but only refused when `n > 1`, so the single-match case (the common one) sailed through, and a widget that merely shares a name with some column elsewhere would be a false positive. Guard belongs in the **mutator**, not the executor — `FindWidget` matches columns too, so an executor-level check on it only catches names that are absent entirely, which the mutator already refuses. Repro `mdl-examples/bug-tests/891-alter-page-bare-column-target.fail.mdl`. Issue #891 | +| `DESCRIBE PAGE` renders an **Accordion group** (or any pluggable widget's object-list item) **empty** — the group's own properties print, its nested widgets do not — so a describe→exec round-trip silently DELETES whatever was inside | An object-list item can carry child widgets in a **Widgets-typed sub-property** (the group's `content` / `headerContent` slot). `extractObjectListItem` handled only scalar sub-properties (datasource, attribute, expression, text template, primitive) and fell through on everything else, so children were never read; and the emitter always closed an item with `"\n"`, so they had nowhere to go even once read. Both halves must change — reading without emitting still prints an empty group | `mdl/executor/cmd_pages_describe_objectlist.go` (`rawObjectListItem.Children`, `extractObjectListItem`), `mdl/executor/cmd_pages_describe_output.go` (the object-list item loop) | Parse a `Widgets` array with `parseRawWidget` — the same recursion the rest of DESCRIBE uses — and emit the item with a `{ … }` body, recursing through `outputWidgetMDLV3` so nesting and indentation stay consistent. Also relax the "keep this item" test to include `len(item.Children) > 0`, or a group whose only content is widgets is dropped wholesale. **The Accordion ships in every blank app** (`widgets/com.mendix.widget.web.Accordion.mpk`) — do not conclude it needs a marketplace install because `modelsdk/widgets/definitions/` has no `accordion.def.json`; that is mxcli's *bundled* registry, while project widgets are discovered from the MPK into `.mxcli/widgets/`. Author one with `PLUGGABLEWIDGET 'com.mendix.widget.web.accordion.Accordion'` (`ACCORDION` is the catalog's MdlName but not a parser keyword). Repro `mdl-examples/bug-tests/891-accordion-group-nested-widgets.mdl`. Issue #891 | diff --git a/CHANGELOG.md b/CHANGELOG.md index 157f75c39..c1548a207 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Fixed +- **`DESCRIBE PAGE` no longer renders an Accordion group empty** (#891) — an object-list item's child widgets (the group's `content` slot) were never read, and the emitter had no body to put them in, so a group holding a DataGrid2 described as a bare `group group1 (…)` and a describe→exec round-trip silently deleted the grid. Both halves are fixed, and the description now re-parses with the nested widgets intact. Applies to any pluggable widget's object-list items, not just Accordion. + - **`ALTER PAGE INSERT`/`REPLACE` no longer corrupts a page when a DataGrid2 column is named without its grid** (#891) — `REPLACE NextRunAt WITH { COLUMN … }` reported success while writing a layout container into the grid's column list, leaving a project Studio Pro and mxbuild could not **load** (`InvalidCastException: DivContainer → WidgetObject`). `DESCRIBE PAGE` skipped the malformed node, so REPLACE looked like a clean deletion and INSERT like a harmless no-op; both were corruption, and neither required the grid to be nested in a pluggable widget. A bare name that resolves to an object-list item is now refused, naming the qualified `grid.column` form — which always worked and is unaffected. - **`DROP FOLDER` no longer orphans the documents inside it** (#892) — the command's contract ("the folder must be empty") existed only in a comment; nothing checked, so dropping a populated folder left every document pointing at a container that no longer existed. Nothing was deleted: the documents were **orphaned**, losing their module qualification (`FeedbackModule.IMM_PostResponse` → `.IMM_PostResponse`) so nothing could resolve them and mxbuild reported CE1613. Reproduced on a *stock* blank app, where `FeedbackModule/Private/Resources/Mappings` holds four documents. The drop is now refused, naming what is inside. The guard reads the type-agnostic unit list rather than the per-kind document lists, so it cannot inherit the blind spot that caused the bug, and it fails closed when contents cannot be determined. diff --git a/mdl-examples/bug-tests/891-accordion-group-nested-widgets.mdl b/mdl-examples/bug-tests/891-accordion-group-nested-widgets.mdl new file mode 100644 index 000000000..b2a0302a2 --- /dev/null +++ b/mdl-examples/bug-tests/891-accordion-group-nested-widgets.mdl @@ -0,0 +1,53 @@ +-- upstream #891 (1) — DESCRIBE PAGE rendered an Accordion group EMPTY. +-- +-- Reproduces on a stock blank app: the Accordion widget ships in every Mendix +-- project (widgets/com.mendix.widget.web.Accordion.mpk), so no marketplace +-- install is needed. +-- +-- Before the fix, describing this page emitted the group with its own +-- properties and NOTHING else: +-- +-- pluggablewidget 'com.mendix.widget.web.accordion.Accordion' acc1 (...) { +-- group group1 (HeaderRenderMode: 'text', ...) +-- } +-- +-- The grid was genuinely in the model (a CustomWidgets$CustomWidget of type +-- com.mendix.widget.web.datagrid.Datagrid, with its AttributeRefs), so a +-- describe -> exec round-trip silently DELETED it. +-- +-- Cause: an object-list item (Accordion group, PopupMenu basicItem) can hold +-- child widgets in a Widgets-typed sub-property (`content`), but +-- extractObjectListItem read only scalar sub-properties, and the emitter always +-- closed an item with "\n" so children had nowhere to go. +-- +-- Verify: +-- mxcli exec 891-accordion-group-nested-widgets.mdl -p app.mpr +-- mxcli -p app.mpr -c "DESCRIBE PAGE A91.AccPage" # the datagrid must appear +-- # INSIDE group group1 +-- -- and the description must round-trip: +-- mxcli -p app.mpr -c "DESCRIBE PAGE A91.AccPage" \ +-- | sed -n '/^create or modify page/,/^}/p' > rt.mdl +-- mxcli check rt.mdl # passes +-- mxcli exec rt.mdl -p app.mpr # grid still there +-- +-- NOTE: authoring an Accordion through the generic PLUGGABLEWIDGET path +-- currently also raises CE0463 on the widget itself. That is a separate, +-- pre-existing authoring gap (the widget object does not match the Accordion's +-- PropertyTypes) — it is present before and after this fix, and the reporter's +-- own page was authored in Studio Pro, where it does not occur. + +create module A91; +/ +create entity A91.Item ( Name: String(100), Qty: Integer ); +/ +create page A91.AccPage ( Title: 'Acc', Layout: Atlas_Core.Atlas_Default ) +{ + PLUGGABLEWIDGET 'com.mendix.widget.web.accordion.Accordion' acc1 { + GROUP g1 (Caption: 'Group one') { + DATAGRID grid1 (DataSource: DATABASE A91.Item) { + COLUMN Name (Attribute: Name, Caption: 'Name') + COLUMN Qty (Attribute: Qty, Caption: 'Qty') + } + } + } +} diff --git a/mdl/executor/cmd_pages_describe_objectlist.go b/mdl/executor/cmd_pages_describe_objectlist.go index cd2099569..5c9be7728 100644 --- a/mdl/executor/cmd_pages_describe_objectlist.go +++ b/mdl/executor/cmd_pages_describe_objectlist.go @@ -18,6 +18,11 @@ type rawObjectList struct { type rawObjectListItem struct { Props []rawExplicitProp DataSource *rawDataSource + // Children are the widgets nested in a Widgets-typed sub-property of the + // item — an Accordion group's `content` / `headerContent` slot. Without + // these an accordion described as an empty group, and re-executing that + // description deleted whatever was inside it (#891). + Children []rawWidget } // extractObjectLists reconstructs every object-list property of a pluggable @@ -67,7 +72,7 @@ func extractObjectLists(ctx *ExecContext, w map[string]any) []rawObjectList { continue } item := extractObjectListItem(ctx, om, nestedMap) - if len(item.Props) > 0 || item.DataSource != nil { + if len(item.Props) > 0 || item.DataSource != nil || len(item.Children) > 0 { items = append(items, item) } } @@ -150,6 +155,19 @@ func extractObjectListItem(ctx *ExecContext, itemObj map[string]any, nestedMap m } continue } + // Child widgets (an Accordion group's `content` slot). A Widgets-typed + // sub-property holds a widget tree, not a scalar, so it is parsed with the + // same recursion the rest of DESCRIBE uses rather than stringified (#891). + if childElems := getBsonArrayElements(value["Widgets"]); len(childElems) > 0 { + for _, ce := range childElems { + cm, ok := ce.(map[string]any) + if !ok { + continue + } + item.Children = append(item.Children, parseRawWidget(ctx, cm)...) + } + continue + } // Attribute binding (staticXAttribute, staticYAttribute, …). if attrRef, ok := value["AttributeRef"].(map[string]any); ok && attrRef != nil { if a := extractString(attrRef["Attribute"]); a != "" { diff --git a/mdl/executor/cmd_pages_describe_objectlist_children_test.go b/mdl/executor/cmd_pages_describe_objectlist_children_test.go new file mode 100644 index 000000000..a2427dc36 --- /dev/null +++ b/mdl/executor/cmd_pages_describe_objectlist_children_test.go @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Issue #891 (1): DESCRIBE PAGE renders an Accordion group empty. +// +// An object-list item (an Accordion `group`, a PopupMenu `basicItem`) can carry +// child WIDGETS in a Widgets-typed sub-property — the group's `content` slot. +// extractObjectListItem handled only scalar sub-properties (datasource, +// attribute, expression, text template, primitive), so those children were +// never read, and the emitter always closed the item with "\n" so they had +// nowhere to go even if they had been. +// +// The grid really is in the model — the reporter's BSON dump showed it, and so +// does a stock blank app: describing an accordion whose group holds a DataGrid2 +// emits `group group1 ( ...props... )` and nothing else. Feeding that +// description back through `exec` silently deletes the grid, which is what +// makes this worth more than a cosmetic gap. +package executor + +import ( + "bytes" + "strings" + "testing" +) + +// buildAccordionWithNestedWidget mirrors the shape Mendix stores: a `groups` +// object-list whose item type declares a `header` text template and a `content` +// Widgets slot, with one child widget inside that slot. +func buildAccordionWithNestedWidget() map[string]any { + const ( + idGroups = "type-id-groups" + idHeader = "type-id-header" + idContent = "type-id-content" + ) + + return map[string]any{ + "Name": "acc1", + "Type": map[string]any{ + "WidgetId": "com.mendix.widget.web.accordion.Accordion", + "ObjectType": map[string]any{ + "PropertyTypes": []any{ + map[string]any{ + "$ID": idGroups, "PropertyKey": "groups", + "ValueType": map[string]any{ + "ObjectType": map[string]any{ + "PropertyTypes": []any{ + map[string]any{"$ID": idHeader, "PropertyKey": "header", + "ValueType": map[string]any{"Type": "TextTemplate"}}, + map[string]any{"$ID": idContent, "PropertyKey": "content", + "ValueType": map[string]any{"Type": "Widgets"}}, + }, + }, + }, + }, + }, + }, + }, + "Object": map[string]any{ + "Properties": []any{ + map[string]any{ + "TypePointer": idGroups, + "Value": map[string]any{ + "Objects": []any{ + map[string]any{ + "Properties": []any{ + map[string]any{ + "TypePointer": idHeader, + "Value": map[string]any{ + "TextTemplate": map[string]any{ + "Template": map[string]any{ + "Items": []any{ + map[string]any{"Text": "Group one"}, + }, + }, + }, + }, + }, + map[string]any{ + "TypePointer": idContent, + "Value": map[string]any{ + "Widgets": []any{ + map[string]any{ + "$Type": "Forms$StaticText", + "Name": "txtInsideGroup", + "Text": map[string]any{ + "Items": []any{ + map[string]any{"Text": "hello"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +// The read half: the child widget must survive extraction. +func TestObjectListItem_KeepsNestedChildWidgets(t *testing.T) { + lists := extractObjectLists(nil, buildAccordionWithNestedWidget()) + if len(lists) != 1 { + t.Fatalf("expected 1 object list, got %d", len(lists)) + } + if len(lists[0].Items) != 1 { + t.Fatalf("expected 1 group, got %d", len(lists[0].Items)) + } + item := lists[0].Items[0] + if len(item.Children) == 0 { + t.Fatal("the group's nested widget was dropped — DESCRIBE would emit an empty group " + + "and a re-exec of that output would delete the widget (#891)") + } + if got := item.Children[0].Name; got != "txtInsideGroup" { + t.Errorf("nested child Name = %q, want %q", got, "txtInsideGroup") + } +} + +// The emit half: an item carrying children must be written with a body. Testing +// through outputWidgetMDLV3 rather than a formatting helper means deleting the +// emit change fails this test — a helper-level assertion would prove the helper +// works and nothing about the wiring. +func TestObjectListItem_EmitsNestedChildWidgets(t *testing.T) { + lists := extractObjectLists(nil, buildAccordionWithNestedWidget()) + if len(lists) == 0 || len(lists[0].Items) == 0 { + t.Fatal("fixture produced no object-list items") + } + + var buf bytes.Buffer + ctx := &ExecContext{Output: &buf} + outputWidgetMDLV3(ctx, rawWidget{ + Type: "CustomWidgets$CustomWidget", + RenderMode: "accordion", + Name: "acc1", + WidgetID: "com.mendix.widget.web.accordion.Accordion", + ObjectLists: lists, + }, 0) + + out := buf.String() + if !strings.Contains(out, "txtInsideGroup") { + t.Errorf("nested widget missing from DESCRIBE output:\n%s", out) + } + // A body, not a bare item line — otherwise the output cannot re-parse. + if !strings.Contains(out, "group group1") || !strings.Contains(out, "{") { + t.Errorf("group should be emitted with a body:\n%s", out) + } +} diff --git a/mdl/executor/cmd_pages_describe_output.go b/mdl/executor/cmd_pages_describe_output.go index 9eea959b1..8d783d966 100644 --- a/mdl/executor/cmd_pages_describe_output.go +++ b/mdl/executor/cmd_pages_describe_output.go @@ -730,6 +730,17 @@ func outputWidgetMDLV3(ctx *ExecContext, w rawWidget, indent int) { itemProps = append(itemProps, fmt.Sprintf("%s: %s", p.Key, mdlQuote(p.Value))) } } + // An item holding child widgets (an Accordion group's `content` + // slot) needs a body, or the children have nowhere to go and the + // description silently drops them on re-exec (#891). + if len(item.Children) > 0 { + formatWidgetProps(ctx.Output, childPrefix, itemHeader, itemProps, " {\n") + for _, child := range item.Children { + outputWidgetMDLV3(ctx, child, indent+2) + } + fmt.Fprintf(ctx.Output, "%s}\n", childPrefix) + continue + } formatWidgetProps(ctx.Output, childPrefix, itemHeader, itemProps, "\n") } } From bb670724aee260f4efb747f20ea45258548bb846 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Aug 2026 08:13:13 +0000 Subject: [PATCH 04/22] Keep an additive chain's operators in the order they were written MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `$A - $B + 1` was stored in the .mxunit as `$A + $B - 1`, and the running app computed the swapped value. The ledger's caption rendered "48620 months" for a span of 20: 24320 + 24301 - 1. buildAdditiveExpression read AllPLUS() and AllMINUS() as two separate token lists and emitted every plus before every minus, discarding the order they appeared in. The comment said as much — "a simplified approach - for complex expressions we'd need to track token positions". The precise rule is that the chain is re-sorted, all `+` ahead of all `-`. That is sharper than "a `-` followed by a `+` swaps", and it predicts which cases come through intact: an all-minus chain has no plus to float ahead, and `+` before `-` is already the order the broken code emitted. The fix was already in the file, twenty lines below: buildMultiplicativeExpression walks GetChildren() in order and builds its operator list correctly. This is that pattern applied to the additive case, so no new mechanism. Two things make this class of defect nastier than it looks: - The corruption is in the stored model, not in DESCRIBE. `strings` on the .mxunit shows the swapped text, which is why the runtime computes it. Confirmed on 11.12.1. - A rewritten expression is perfectly valid, so nothing downstream can catch it. `mxcli check` passes, `mx check` reports 0 errors, the build succeeds and the microflow runs. The only symptom is the number, and a number is the thing a reader assumes is right. The ledger caught this one because 48620 months is absurd; out by two, it would have shipped. The only test that works is round-trip equality — "does it apply cleanly" proves nothing here. The control cases carry the weight: `$A - $B - 1` and `$A + $B - 1` pass before and after, so a suite built only from the failing cases would have gone green against code that sorted all minuses first instead. Verified by reverting the fix and confirming exactly the four swapped cases fail with the reported symptom. All eleven expressions in the example now round-trip verbatim through DESCRIBE MICROFLOW, with mx check at 0 errors. Reported in mxcli-ledger FINDINGS #105. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4 --- .claude/skills/fix-issue.md | 1 + .../bug-tests/additive-operator-order.mdl | 54 ++++++++++ mdl/visitor/visitor_additive_order_test.go | 100 ++++++++++++++++++ mdl/visitor/visitor_microflow_expression.go | 32 +++--- 4 files changed, 175 insertions(+), 12 deletions(-) create mode 100644 mdl-examples/bug-tests/additive-operator-order.mdl create mode 100644 mdl/visitor/visitor_additive_order_test.go diff --git a/.claude/skills/fix-issue.md b/.claude/skills/fix-issue.md index 7bbcb07fb..7ebcf0e38 100644 --- a/.claude/skills/fix-issue.md +++ b/.claude/skills/fix-issue.md @@ -515,3 +515,4 @@ extracting `OffsetExpression`/`LimitExpression`. | `DESCRIBE PAGE` output will not re-parse for any page carrying a **pluggable widget**: `mxcli check` on it fails with `extraneous input ':'` / `extraneous input '('` from the first widget onward. Separately, a boolean property the author set is missing from the description entirely | Two independent defects in the same path. **Emit**: explicit properties were written with a raw `%s`, so every string lost its quotes — a JSON `spec: {"a": 1}` then broke the parse at its first brace. **Read**: `extractExplicitProperties` skipped any value of `"true"`/`"false"` as a "common default", so booleans never reached the output | `mdl/executor/cmd_pages_describe_output.go` (`explicitPropValue`, `isBareLiteral`), `mdl/executor/cmd_pages_describe_pluggable.go` (`buildPropertyValueTypeMap`, `extractExplicitProperties`), `mdl/executor/cmd_pages_describe.go` (`rawExplicitProp.ValueType`) | **Fixing one half alone is worse than the bug.** Quote without emitting booleans and the description re-parses cleanly while silently dropping a property — a wrong page that validates. Both halves ship together or neither. **Quote by the DECLARED type, never the value's shape**: `ValueType.Type` sits in the widget's `Type.ObjectType.PropertyTypes`, the same array `buildPropertyTypeKeyMap` already walks for `PropertyKey` and throws away; a String property holding `"30"` or `"true"` is indistinguishable from a number once it is a string in BSON, and must still come back quoted. Where no type is declared, fall back to the value's shape and quote anything not plainly numeric or boolean — quoting is the safe direction, since an unquoted arbitrary string may not parse at all. **The round trip is the test, not the output**: describe → `check` → `exec` → describe must be byte-identical and leave `mx check` at 0 errors. Tests `mdl/executor/cmd_pages_describe_pluggable_roundtrip_test.go`; example `mdl-examples/bug-tests/pluggable-describe-roundtrip.mdl`, verified end to end on 11.12.1. **Uncovered while verifying, NOT fixed**: giving a property in a conditionally shown group a non-default value writes a widget Mendix rejects with CE0463 — ProgressCircle's `showLabel: true` and `labelType: 'percentage'` both do it with no DESCRIBE involved, while the same widget's General-group properties take non-default values happily. Reported in mxcli-ledger FINDINGS #104 | | `DESCRIBE PAGE` output will not re-parse for any page carrying a **pluggable widget**: `mxcli check` on it fails with `extraneous input ':'` / `extraneous input '('` from the first widget onward. Separately, a boolean property the author set is missing from the description entirely | Two independent defects in the same path. **Emit**: explicit properties were written with a raw `%s`, so every string lost its quotes — a JSON `spec: {"a": 1}` then broke the parse at its first brace. **Read**: `extractExplicitProperties` skipped any value of `"true"`/`"false"` as a "common default", so booleans never reached the output | `mdl/executor/cmd_pages_describe_output.go` (`explicitPropValue`, `isBareLiteral`), `mdl/executor/cmd_pages_describe_pluggable.go` (`buildPropertyValueTypeMap`, `extractExplicitProperties`), `mdl/executor/cmd_pages_describe.go` (`rawExplicitProp.ValueType`) | **Fixing one half alone is worse than the bug.** Quote without emitting booleans and the description re-parses cleanly while silently dropping a property — a wrong page that validates. Both halves ship together or neither. **Quote by the DECLARED type, never the value's shape**: `ValueType.Type` sits in the widget's `Type.ObjectType.PropertyTypes`, the same array `buildPropertyTypeKeyMap` already walks for `PropertyKey` and throws away; a String property holding `"30"` or `"true"` is indistinguishable from a number once it is a string in BSON, and must still come back quoted. Where no type is declared, fall back to the value's shape and quote anything not plainly numeric or boolean — quoting is the safe direction, since an unquoted arbitrary string may not parse at all. **The round trip is the test, not the output**: describe → `check` → `exec` → describe must be byte-identical and leave `mx check` at 0 errors. Tests `mdl/executor/cmd_pages_describe_pluggable_roundtrip_test.go`; example `mdl-examples/bug-tests/pluggable-describe-roundtrip.mdl`, verified end to end on 11.12.1. Reported in mxcli-ledger FINDINGS #104 | | Authoring a pluggable widget property that lives in a **conditionally shown group** writes a widget Mendix rejects with **CE0463**, while the same widget's other properties take non-default values happily. On ProgressCircle both `showLabel: true` and `labelType: 'percentage'` do it; `showLabel: false` and General-group properties are clean. No DESCRIBE involved | Two gaps on the same axis. **Serialization**: #574 nulls the TextTemplate of a HIDDEN conditional property, but left a VISIBLE one null — and Mendix stores an empty `Forms$ClientTemplate` there. Null and empty are each invalid in the other's state, so nulling hidden ones was only half the rule. **Extraction**: the editorConfig reader did not understand a ternary's ELSE branch (`cond ? (…) : hidePropertiesIn([…])`), so the `showLabel` gate was never seen and `labelText` read as visible whenever `labelType` was `"text"` — its default | `mdl/backend/widgetobj/builder.go` (`ApplyVisibilityRules`, `bsonFieldIsNil`), `mdl/executor/editorconfig_extract.go` (`parseGuard` `:` case, `ternaryCondition`, `trailingExpr`) | **Fixing one gap alone inverts the bug rather than closing it** — filling visible templates without the missing gate made `showLabel: false` fail where `true` had, because mxcli still thought labelText was visible. Measured both ways round before and after; a fix that moves which case fails is not a fix. **Let Mendix say what the shape should be**: `mx update-widgets` on a COPY of the failing project reconciles the widget, and diffing that against mxcli's output named the single meaningful path (`Object/Properties[N]/Value/TextTemplate` null vs `Forms$ClientTemplate`) out of 969. **The good/bad control does the isolation for free**: authoring the same widget with the boolean both ways gave two documents differing in exactly ONE path, so no other candidate needed testing. **Only CONDITIONAL properties are filled** — Studio Pro's convention for an unset TextTemplate is not uniform (a DataGrid custom-content column stores null for `tooltip` and an empty template for `exportValue`, per `emptyClientTemplateRules`), so filling every unset one would trade this bug for its mirror image. **The extractor's preamble matters**: the ternary is preceded by a whole `switch`, and walking back past the `?` to the function start yields a fragment with an unbalanced `}` that parses to nothing and looks like "unsupported shape" — hence `trailingExpr`. Regression signal: the widgetdemo showcase applies with **0 CE0463** (its 4 CE1613 are a pre-existing attribute reference). Tests `mdl/backend/widgetobj/widget_visibility_test.go`, `mdl/executor/editorconfig_extract_test.go`; example `mdl-examples/bug-tests/pluggable-describe-roundtrip.mdl` now exercises the group. Reported in mxcli-ledger FINDINGS #104 follow-on | +| A microflow computes a **different number than the expression says**, while `mxcli check`, `mx check` and the build are all green. An additive chain comes back from `DESCRIBE MICROFLOW` with its `+` and `-` exchanged — `$A - $B + 1` stored as `$A + $B - 1`. All-plus and all-minus chains are fine, as is `-` against `*` | `buildAdditiveExpression` read `AllPLUS()` and `AllMINUS()` as two separate token lists and emitted **every plus before every minus**, discarding source order. The precise rule is "the chain is re-sorted, all `+` ahead of all `-`" — sharper than "a `-` followed by a `+` swaps", and it predicts which cases survive | `mdl/visitor/visitor_microflow_expression.go` (`buildAdditiveExpression`) | **The fix already existed 20 lines below**: `buildMultiplicativeExpression` walks `GetChildren()` in order and builds its operator list correctly, so the additive case is that pattern copied — no new mechanism. The corruption is in the **stored model**, not in DESCRIBE: `strings` on the `.mxunit` shows the swapped text, which is why the running app computes it. A rewritten expression is perfectly valid, so no validator can catch this class — the only test that works is round-trip equality, not "does it apply cleanly". **The control cases carry the weight**: `$A - $B - 1` and `$A + $B - 1` pass both before and after, so a test built only from failing cases would have passed against code that sorted all minuses first instead. Verified by reverting the fix and confirming exactly the four swapped cases fail. Test `mdl/visitor/visitor_additive_order_test.go`; example `mdl-examples/bug-tests/additive-operator-order.mdl`. Reported in mxcli-ledger FINDINGS #105 | diff --git a/mdl-examples/bug-tests/additive-operator-order.mdl b/mdl-examples/bug-tests/additive-operator-order.mdl new file mode 100644 index 000000000..2542aa385 --- /dev/null +++ b/mdl-examples/bug-tests/additive-operator-order.mdl @@ -0,0 +1,54 @@ +-- ledger #105 — an additive chain must be stored with its operators in the +-- order they were written. +-- +-- buildAdditiveExpression read the `+` and `-` tokens as two separate lists and +-- emitted every plus before every minus, so `$A - $B + 1` was rebuilt as +-- `$A + $B - 1` and written to the .mxunit that way. The ledger's app rendered +-- "48620 months" for a span of 20, because 24320 + 24301 - 1 is what the runtime +-- was actually given. +-- +-- What makes it worth an example rather than only a unit test: every layer +-- downstream is happy. `mxcli check` passes, `mx check` reports 0 errors, the +-- build succeeds and the microflow runs. The rewritten expression is valid — it +-- is simply not the one anyone wrote. So the check is not "does this apply +-- cleanly" but "does DESCRIBE give back what went in": +-- +-- mxcli exec mdl-examples/bug-tests/additive-operator-order.mdl -p app.mpr +-- mxcli -p app.mpr -c "DESCRIBE MICROFLOW MyFirstModule.ACT_AdditiveOrder" +-- +-- Every `$R = ...` line below must come back verbatim. Before the fix, four of +-- them did not. + +create or replace microflow MyFirstModule.ACT_AdditiveOrder ($A: integer, $B: integer, $C: integer) +returns integer as $R +begin + declare $R integer = 0; + + -- These two were never affected: a single operator has no order to lose. + $R = $A - $B; + $R = $A + $B; + + -- The bug. A `-` written before a `+` came back with the two exchanged. + $R = $A - $B + 1; + $R = $A - $B + $C; + $R = $A - $B + $C - 2; + $R = 1 - $A + $B; + + -- These survived it, and are here as the control. An all-minus chain has no + -- plus to float ahead, and `+` before `-` is already the order the broken + -- code emitted — so a test built only from the failing cases above would + -- have passed against an implementation that sorted the other way round. + $R = $A - $B - 1; + $R = $A + $B - 1; + $R = $A + $B + $C; + + -- Multiplication binds tighter, so it never joins the additive chain. This + -- one was always correct and stays correct. + $R = $A - $B * 2; + + -- A parenthesised subtraction is its own chain, likewise unaffected. + $R = $A - ($B - 1); + + return $R; +end; +/ diff --git a/mdl/visitor/visitor_additive_order_test.go b/mdl/visitor/visitor_additive_order_test.go new file mode 100644 index 000000000..257606932 --- /dev/null +++ b/mdl/visitor/visitor_additive_order_test.go @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 + +package visitor + +import ( + "strings" + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" +) + +// ledger #105 — an additive chain must keep the operators in the order they were +// written. buildAdditiveExpression used to read AllPLUS() and AllMINUS() as two +// separate token lists and emit every `+` before every `-`, so `$A - $B + 1` was +// REBUILT as `$A + $B - 1` and stored that way. +// +// This is the worst shape a defect can take here: the rewritten expression is +// perfectly valid, so `mxcli check`, `mx check` and the build are all green, and +// the only symptom is that the running app computes a different number. The +// ledger caught it because a month span rendered as 48620. +// +// The cases are the ledger's own probe. Note which ones survived the bug — +// `$A - $B - 1` (no `+` to float ahead) and `$A + $B - 1` (already in that +// order) — because a test built only from failing cases would have passed +// against an implementation that emits all minuses first instead. +func TestAdditiveChainKeepsWrittenOperatorOrder(t *testing.T) { + cases := []struct { + expr string + want []string // operators, in source order + }{ + {"$A - $B", []string{"-"}}, + {"$A + $B", []string{"+"}}, + {"$A - $B + 1", []string{"-", "+"}}, // swapped before the fix + {"$A - $B - 1", []string{"-", "-"}}, // survived the bug + {"$A + $B - 1", []string{"+", "-"}}, // survived the bug + {"$A - $B + $C", []string{"-", "+"}}, // swapped before the fix + {"$A - $B + $C - 2", []string{"-", "+", "-"}}, // swapped before the fix + {"1 - $A + $B", []string{"-", "+"}}, // swapped before the fix + {"$A + $B + $C", []string{"+", "+"}}, + {"$A - $B - $C + 1 + 2", []string{"-", "-", "+", "+"}}, + } + + for _, tc := range cases { + t.Run(tc.expr, func(t *testing.T) { + src := "create microflow M.Mf ($A: integer, $B: integer, $C: integer) returns integer as $R begin declare $R integer = 0; $R = " + + tc.expr + "; return $R; end" + prog, errs := Build(src) + if len(errs) > 0 { + t.Fatalf("parse errors for %q: %v", tc.expr, errs) + } + mf := prog.Statements[0].(*ast.CreateMicroflowStmt) + + var assigned ast.Expression + for _, s := range mf.Body { + if set, ok := s.(*ast.MfSetStmt); ok { + assigned = set.Value + } + } + if assigned == nil { + t.Fatalf("no assignment found in %q", src) + } + + got := additiveOperators(assigned) + if strings.Join(got, ",") != strings.Join(tc.want, ",") { + t.Errorf("%q built operators %v, want %v (source order)", tc.expr, got, tc.want) + } + }) + } +} + +// TestAdditiveOperatorsIgnoresTighterBinding pins the reason `$A - $B * 2` was +// never affected: multiplication does not join the additive chain, so the +// helper below must not walk into it. Without this, a regression in the +// multiplicative builder would show up as a confusing failure in the additive +// test rather than its own. +func TestAdditiveOperatorsIgnoresTighterBinding(t *testing.T) { + prog, errs := Build("create microflow M.Mf ($A: integer, $B: integer) returns integer as $R begin declare $R integer = 0; $R = $A - $B * 2; return $R; end") + if len(errs) > 0 { + t.Fatalf("parse errors: %v", errs) + } + mf := prog.Statements[0].(*ast.CreateMicroflowStmt) + for _, s := range mf.Body { + if set, ok := s.(*ast.MfSetStmt); ok { + if got := additiveOperators(set.Value); strings.Join(got, ",") != "-" { + t.Errorf("`$A - $B * 2` additive operators = %v, want [-]", got) + } + } + } +} + +// additiveOperators flattens a left-nested BinaryExpr chain of + and - into the +// operators in source order. It stops at any other operator, so a tighter-binding +// sub-expression is one opaque operand rather than part of the chain. +func additiveOperators(e ast.Expression) []string { + bin, ok := e.(*ast.BinaryExpr) + if !ok || (bin.Operator != "+" && bin.Operator != "-") { + return nil + } + return append(additiveOperators(bin.Left), bin.Operator) +} diff --git a/mdl/visitor/visitor_microflow_expression.go b/mdl/visitor/visitor_microflow_expression.go index b119922c2..98563f14b 100644 --- a/mdl/visitor/visitor_microflow_expression.go +++ b/mdl/visitor/visitor_microflow_expression.go @@ -162,21 +162,29 @@ func buildAdditiveExpression(ctx parser.IAdditiveExpressionContext) ast.Expressi // Build first operand result := buildMultiplicativeExpression(multExprs[0]) - // Get operators (PLUS and MINUS tokens) - plusTokens := addCtx.AllPLUS() - minusTokens := addCtx.AllMINUS() + // Get operators from children in order, exactly as buildMultiplicativeExpression + // does below. Reading AllPLUS() and AllMINUS() as two separate lists loses the + // order they were written in: every `+` floats ahead of every `-`, so + // `$A - $B + 1` is REBUILT as `$A + $B - 1` and stored that way. The app then + // computes the swapped value while mxcli check, mx check and the build all stay + // green, because the corrupted expression is perfectly valid. (ledger #105) + var operators []string + for _, child := range addCtx.GetChildren() { + if term, ok := child.(antlr.TerminalNode); ok { + switch term.GetSymbol().GetTokenType() { + case parser.MDLParserPLUS: + operators = append(operators, "+") + case parser.MDLParserMINUS: + operators = append(operators, "-") + } + } + } - // Reconstruct the sequence of operators - // This is a simplified approach - for complex expressions we'd need to track token positions - opIndex := 0 for i := 1; i < len(multExprs); i++ { - op := "+" - if opIndex < len(plusTokens) { - op = "+" - } else if opIndex-len(plusTokens) < len(minusTokens) { - op = "-" + op := "+" // default + if i-1 < len(operators) { + op = operators[i-1] } - opIndex++ result = &ast.BinaryExpr{ Left: result, From 4828f81003cc8ded4f83b95ac43639d043c89f4b Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Aug 2026 08:23:46 +0000 Subject: [PATCH 05/22] Ship skill packs: a skill that carries assets, not just prose MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mxcli ships 65 skills and every one is a single Markdown file. That was never a design decision — it is what the mechanism can carry. The mxcli-ledger project has produced blocks that do not fit: a Vega-Lite charting pack with seven spec templates and a headless checker, and an OQL bulk-DML pack with three Java actions applied through MDL. Four independent places blocked them, and the write path is the one with teeth: - Embed `//go:embed skills/*.md` — flat, .md only - Sync flat `for f in ...*.md` in the Makefile - Write filepath.Join(dir, d.Name()) — BASENAME. Nesting is flattened, so references/install.md and specs/install.md silently collide - Refresh syncAIContextSkills skips directories outright, so a pack would never follow a binary upgrade The flattening does not error. It produces a plausible-looking directory with a file quietly missing, which is the failure mode this repo keeps writing down: a tool accepting what it does not implement is worse than one that rejects it. This adds cmd/mxcli/skillpack, `mxcli skill list|add|remove|upgrade`, and vendors the first pack. Three decisions worth stating: **Packs are opt-in; skills are not.** The 65 prose skills are free to write into every project. A pack is not: this one adds Java actions to the model, and a charting pack needs a widget installed. So copying a pack never touches the model — `skill add` writes files and prints the command that would apply the MDL, for the user to run deliberately. **`all:` on the embed is load-bearing, not defensive.** A plain go:embed of a directory skips `_`- and `.`-prefixed files. cmd/mxcli/theme/assets.go carries the same prefix because `_partial.scss` is how SCSS spells a partial and the theme package lost them once. A pack is just as likely to ship a `_helper.mjs`. **Install prunes.** The existing sync overwrites but never deletes, so a pack dropping an asset in v2 would leave v1's behind forever — and a stale spec template is worse than a missing one, because it still looks current. The tests drive the hazards rather than the happy path: two files both named install.md in different subdirectories (the flattening case), a v2 pack that drops files (the prune case), a second install that must write nothing, and a pack directory that must not be reachable from another's prune. The traversal test earned its place immediately — it caught that ".." survives `name == filepath.Base(filepath.Clean(name))`, which would have let `skill remove ..` RemoveAll the whole skills directory. The pack's own MDL is now checked by `make check-skill-mdl`; the existing script only reads fenced blocks in markdown and never saw it. A pack whose verifier is not run in CI is a pack that rots. Deferred, and named in the proposal: digest-fenced refusal of locally edited files (theme/ already does this and packs should reuse it), `--apply` actually executing installs.mdl, `init --with`, and the Vega pack, which cannot be vendored until its widget is re-published away from the ledger's `ledger.widget.web.*` namespace. Design and rationale: docs/11-proposals/PROPOSAL_skill_packs.md Pack source: https://github.com/ako/mxcli-ledger Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4 --- .claude/skills/packs/README.md | 49 +++ .../skills/packs/mendix-bulk-oql-dml/SKILL.md | 119 +++++++ .../mdl/oql-dml-actions.mdl | 179 ++++++++++ .../packs/mendix-bulk-oql-dml/pack.yaml | 26 ++ .../mendix-bulk-oql-dml/references/gotchas.md | 133 ++++++++ .../references/patterns.md | 156 +++++++++ .gitignore | 1 + Makefile | 21 +- cmd/mxcli/cmd_skill.go | 207 ++++++++++++ cmd/mxcli/skillpack/skillpack.go | 308 ++++++++++++++++++ cmd/mxcli/skillpack/skillpack_test.go | 184 +++++++++++ cmd/mxcli/skills_content.go | 12 + docs/11-proposals/PROPOSAL_skill_packs.md | 187 +++++++++++ 13 files changed, 1580 insertions(+), 2 deletions(-) create mode 100644 .claude/skills/packs/README.md create mode 100644 .claude/skills/packs/mendix-bulk-oql-dml/SKILL.md create mode 100644 .claude/skills/packs/mendix-bulk-oql-dml/mdl/oql-dml-actions.mdl create mode 100644 .claude/skills/packs/mendix-bulk-oql-dml/pack.yaml create mode 100644 .claude/skills/packs/mendix-bulk-oql-dml/references/gotchas.md create mode 100644 .claude/skills/packs/mendix-bulk-oql-dml/references/patterns.md create mode 100644 cmd/mxcli/cmd_skill.go create mode 100644 cmd/mxcli/skillpack/skillpack.go create mode 100644 cmd/mxcli/skillpack/skillpack_test.go create mode 100644 docs/11-proposals/PROPOSAL_skill_packs.md diff --git a/.claude/skills/packs/README.md b/.claude/skills/packs/README.md new file mode 100644 index 000000000..684d68cfe --- /dev/null +++ b/.claude/skills/packs/README.md @@ -0,0 +1,49 @@ +# Skill packs + +Source of truth for skill **packs** — skills that carry more than prose. +`make sync-skill-packs` copies these into `cmd/mxcli/skillpacks/` for embedding +(that directory is gitignored and regenerated; **edit here, not there**). + +A pack is a directory rather than a single Markdown file: + +``` +/ + pack.yaml manifest — name must match the directory + SKILL.md frontmatter name + description, then the body + references/*.md loaded on demand, not in the prompt + specs/ scripts/ mdl/ assets +``` + +Installed with `mxcli skill add `, which writes the tree into a project's +`.claude/skills//`. Design and rationale: +[PROPOSAL_skill_packs.md](../../../docs/11-proposals/PROPOSAL_skill_packs.md). + +## Packs are opt-in; the flat skills in `mendix/` are not + +`.claude/skills/mendix/*.md` are pure prose and `mxcli init` writes every one of +them into every project — worst case an agent reads a page it did not need. + +A pack is not free. `mendix-bulk-oql-dml` ships MDL that adds Java actions to the +model; a charting pack would need a widget installed. So nothing is installed +until asked for, and **copying a pack never touches the model** — `skill add` +writes files and prints the command that would apply the MDL, which the user runs +deliberately. + +## Adding one + +1. **Match the shape above.** `pack.yaml`'s `name` must equal the directory name; + they are checked against each other, because `skill remove ` has to find + what `skill add ` wrote. +2. **Installation steps that have been run**, not described from memory. +3. **Templates with sample inputs**, so the first use is copy-and-edit. +4. **A way to check the work without the full stack** — something with an exit + code, runnable in seconds. +5. **Failure modes, symptoms first.** Every entry one that actually happened. +6. **Keep it project-neutral.** A pack carrying one project's module or widget + namespace hands that namespace to everyone who installs it. Use a placeholder + (`MyModule`) or automate the rename. +7. **Any `mdl/*.mdl` is checked by `make check-skill-mdl`.** A pack whose own MDL + is never checked is a pack that rots. + +The shape and the first two packs come from +[mxcli-ledger](https://github.com/ako/mxcli-ledger/tree/main/.claude/skills). diff --git a/.claude/skills/packs/mendix-bulk-oql-dml/SKILL.md b/.claude/skills/packs/mendix-bulk-oql-dml/SKILL.md new file mode 100644 index 000000000..ef249a1b6 --- /dev/null +++ b/.claude/skills/packs/mendix-bulk-oql-dml/SKILL.md @@ -0,0 +1,119 @@ +--- +name: mendix-bulk-oql-dml +description: Run set-based INSERT, UPDATE and DELETE against Mendix entities through OQL statements, which the runtime supports and Studio Pro cannot author. Use when a microflow would otherwise retrieve a large list and loop — applying rules across a table, copying a year of records, staging and promoting an import, archiving, backfilling a column — or when a nested retrieve-loop-commit is the reason a screen or a scheduled job is slow. +--- + +# Bulk DML through OQL statements + +## What this is + +The Mendix runtime executes OQL **statements**, not just queries. Three calls, all +in `com.mendix.public-api.jar`: + +```java +com.mendix.core.Core.createOqlStatement(String) // -> OqlStatement +OqlStatement.setVariable(name, value) // -> OqlStatement, chainable +OqlStatement.execute(IContext) // -> int rows affected +``` + +Studio Pro has no activity for this, so the only way to reach it is a Java action. +[`mdl/oql-dml-actions.mdl`](mdl/oql-dml-actions.mdl) is three of them, authored in +MDL with inline Java, ready to apply to any project: + +| Action | Use | +|---|---| +| `OQL_Execute(Statement)` | One statement, no variables. Returns rows affected, throws on failure. | +| `OQL_ExecuteWith(Statement, Name/Value/Type ×4)` | Same, with up to four bound, typed variables. | +| `OQL_Try(Statement)` | Returns `OK rows=N` or `ERR …` instead of throwing. For probing, not for production paths. | + +```bash +mxcli exec .claude/skills/mendix-bulk-oql-dml/mdl/oql-dml-actions.mdl -p MyApp.mpr +``` + +## Check the version first + +Each statement type arrived in a different runtime release, and the last one is +recent enough that "it works on my app" is not transferable: + +| Statement | Available from | +|---|---| +| `DELETE` | 11.1.0 | +| `UPDATE` | 11.3.0 — associations 11.4.0 | +| `INSERT … SELECT` | 11.6.0 — associations 11.7.0 | +| `INSERT … VALUES` | 11.13.0 | + +Pin the runtime version deliberately before building on this. + +## When to use it, and when not + +Use it when the work is **a set** and nobody is looking at the rows: applying +rules over a table, copying a year of records, promoting a staged import, +archiving, backfilling a new column. One statement replaces a retrieve of every +match into memory, a loop, and a commit per object. + +Do not use it for a single object a user is editing. And know what it skips — +this is the thing to say out loud before choosing it: + +> A statement runs in the database, inside the calling microflow's transaction. +> It does not pass through the object cache, so **no event handlers fire, no +> validation rules run, an object already retrieved keeps its old values, and a +> client holding one is not refreshed.** + +If the entity's correctness depends on a before-commit handler, either move that +logic into the statement or do not use a statement. + +## Making the result visible + +A grid over rows a statement just rewrote keeps showing the old ones, because +nothing told the client. The pattern that fixes it, used on both screens in this +project: + +1. The screen has a small non-persistent context object. +2. The grid's datasource is a **database** source constrained on that object + (`where [Batch = $currentObject/Batch]`). +3. The action ends with `commit $Context refresh;`. + +Refreshing the context re-runs the grid's query. Without step 3 the screen is +quietly wrong, which is worse than obviously wrong. + +## Patterns that work + +Full statements, from three working use cases, are in +[`references/patterns.md`](references/patterns.md): + +- **First-match-wins rules** — one `UPDATE` per rule, in order. The precedence + lives in the WHERE clause: each statement only touches rows still unclaimed, + so a later rule cannot take a row an earlier one took. No flags, no loop. +- **Copy a year** — `INSERT … SELECT` per month. Idempotent by deleting the + target window first, in the same transaction. +- **Stage and promote** — land rows in a loader entity, validate them *where + they landed* with one `UPDATE` per check stamping a reason on the failures, + then promote the survivors with a single `INSERT … SELECT`. The rejects stay + behind with their reason, which is the whole argument for a loader table. + +## Before writing a statement + +Read [`references/gotchas.md`](references/gotchas.md). Four things cost real time +here, and one of them wrote bad data: + +1. **An association compared to `null` in a WHERE matches nothing** — in both + spellings, with no error. A validation written that way passes every row. +2. **Alias every column in an `INSERT … SELECT`.** Two association paths both + end in `/id` and collide as `Duplicate column name: ID`, naming a column that + is not in your statement. +3. **Association columns must be module-qualified** — `Ledger.Order_Customer`, + not `Order_Customer`. +4. **No `substring`.** String surgery has to be done by the caller, which is why + "copy a year" is twelve statements rather than one. + +## Probing safely + +The grammar is not discoverable from the model, so find out by running. Use +`OQL_Try` with statements whose WHERE cannot match, from a microflow that logs +each result, and read the log. Sixteen statements in one pass is what mapped the +matrix in `references/patterns.md`. + +Two warnings if you use the after-startup microflow as the harness, both learned +the hard way: a statement that throws there **takes the whole app down**, and +the action is one transaction, so a failure at the end **rolls back everything +before it** — including work whose log lines already said it succeeded. diff --git a/.claude/skills/packs/mendix-bulk-oql-dml/mdl/oql-dml-actions.mdl b/.claude/skills/packs/mendix-bulk-oql-dml/mdl/oql-dml-actions.mdl new file mode 100644 index 000000000..c22b6f593 --- /dev/null +++ b/.claude/skills/packs/mendix-bulk-oql-dml/mdl/oql-dml-actions.mdl @@ -0,0 +1,179 @@ +-- ============================================================================ +-- OQL DML through Java actions +-- ============================================================================ +-- Mendix's runtime can execute OQL *statements* — insert, update, delete — and +-- Studio Pro exposes no way to author one. The API is two calls: +-- +-- com.mendix.core.Core.createOqlStatement(String) -> OqlStatement +-- OqlStatement.setVariable(name, value) -> OqlStatement (chainable) +-- OqlStatement.execute(IContext) -> int rows affected +-- +-- Both are in com.mendix.public-api.jar. Check the runtime version first: +-- DELETE needs 11.1, UPDATE 11.3 (associations 11.4), INSERT..SELECT 11.6 +-- (associations 11.7), INSERT..VALUES 11.13. +-- +-- What that buys is set-based work: one statement the database runs, rather +-- than a retrieve of every match into memory, a loop, and a commit per object. +-- +-- Apply with: mxcli exec oql-dml-actions.mdl -p MyApp.mpr +-- Replace MyModule throughout with the module these belong in. +-- +-- The cost is that the object cache does not see it. Nothing that these +-- statements change fires an event handler, runs a validation rule, or refreshes +-- an object a client is already holding — see the note on OQL_Execute. +-- ============================================================================ + +/** + * Execute one OQL DML statement. Returns the number of rows affected. + * + * This is the primitive the rest of the file builds on. It deliberately has no + * variables: a statement with variables goes through OQL_ExecuteWith, so the + * caller has to decide which values are data (bound, escaped) rather than + * getting a string-concatenation habit for free. + * + * WHAT THIS BYPASSES. The statement runs in the database, inside the calling + * microflow's transaction. It does not go through the object cache, so: + * * no before/after-commit event handlers fire, + * * no validation rules run, + * * an object already retrieved in this context keeps its old values, + * * a client holding the object is not refreshed. + * Use it for bulk work over rows nobody is looking at. For a single object a + * user is editing, change and commit the object. + * + * @param Statement An OQL insert, update or delete statement + * @returns Rows affected + */ +create or replace java action MyModule.OQL_Execute ( + Statement: string +) +returns integer +as $$ +if (Statement == null || Statement.trim().isEmpty()) { + throw new IllegalArgumentException("OQL_Execute: statement is empty"); +} +com.mendix.datastorage.OqlStatement stmt = com.mendix.core.Core.createOqlStatement(Statement); +int affected = stmt.execute(getContext()); +com.mendix.core.Core.getLogger("OQL").debug("OQL_Execute affected " + affected + " row(s): " + Statement); +return (long) affected; +$$; +/ + +/** + * Execute one OQL DML statement carrying up to four bound variables. + * + * + * Variables are written $Name in the statement and bound here by name, so a + * value never becomes part of the statement text. That matters for the same + * reason it matters in SQL: a merchant called O'Brien breaks a concatenated + * statement and does not break a bound one. + * + * Types are declared rather than guessed. A slot with an empty name is skipped, + * which is what makes one action serve a statement with one variable and a + * statement with four. + * + * @param Statement An OQL insert, update or delete statement using $Name variables + * @param Name1 Variable name without the $, or empty to skip + * @param Value1 The value, as text; converted according to Type1 + * @param Type1 'string', 'integer', 'decimal', 'boolean', 'datetime' or 'id' + * @returns Rows affected + */ +create or replace java action MyModule.OQL_ExecuteWith ( + Statement: string, + Name1: string, + Value1: string, + Type1: string, + Name2: string, + Value2: string, + Type2: string, + Name3: string, + Value3: string, + Type3: string, + Name4: string, + Value4: string, + Type4: string +) +returns integer +as $$ +if (Statement == null || Statement.trim().isEmpty()) { + throw new IllegalArgumentException("OQL_ExecuteWith: statement is empty"); +} +com.mendix.datastorage.OqlStatement stmt = com.mendix.core.Core.createOqlStatement(Statement); + +String[] names = { Name1, Name2, Name3, Name4 }; +String[] values = { Value1, Value2, Value3, Value4 }; +String[] types = { Type1, Type2, Type3, Type4 }; + +for (int i = 0; i < names.length; i++) { + String name = names[i]; + if (name == null || name.trim().isEmpty()) { + continue; + } + String value = values[i]; + String type = (types[i] == null ? "string" : types[i].trim().toLowerCase()); + switch (type) { + case "integer": + case "long": + stmt.setVariable(name, Long.parseLong(value.trim())); + break; + case "decimal": + stmt.setVariable(name, new java.math.BigDecimal(value.trim())); + break; + case "boolean": + stmt.setVariable(name, Boolean.parseBoolean(value.trim())); + break; + case "datetime": + // ISO-8601 instant, which is what formatDateTime with a UTC pattern emits. + stmt.setVariable(name, java.util.Date.from(java.time.Instant.parse(value.trim()))); + break; + case "id": + stmt.setVariable(name, + com.mendix.core.Core.createMendixIdentifier(Long.parseLong(value.trim()))); + break; + case "string": + default: + stmt.setVariable(name, value); + break; + } +} + +int affected = stmt.execute(getContext()); +com.mendix.core.Core.getLogger("OQL").debug("OQL_ExecuteWith affected " + affected + " row(s): " + Statement); +return (long) affected; +$$; +/ + +/** + * Run a statement and report what happened instead of throwing. + * + * Returns 'OK rows=N' or 'ERR : '. This exists because the + * grammar OQL DML accepts is not documented anywhere the model can see, so + * finding out what a statement does is an experiment, and an experiment that + * rolls the transaction back on the first surprise is a slow experiment. + * + * Not for production paths: a failure that returns a string is a failure the + * caller can ignore. Use OQL_Execute there and let it throw. + * + * @param Statement An OQL statement to try + * @returns 'OK rows=N' or 'ERR ...' + */ +create or replace java action MyModule.OQL_Try ( + Statement: string +) +returns string +as $$ +try { + com.mendix.datastorage.OqlStatement stmt = com.mendix.core.Core.createOqlStatement(Statement); + int affected = stmt.execute(getContext()); + return "OK rows=" + affected; +} catch (Throwable t) { + String message = String.valueOf(t.getMessage()).replace('\n', ' ').replace('\r', ' '); + if (message.length() > 300) { + message = message.substring(0, 300) + "..."; + } + return "ERR " + t.getClass().getSimpleName() + ": " + message; +} +$$; +/ + +-- No grants here. A Java action has no security roles of its own in Mendix — it +-- is reachable only through a microflow, and the microflow carries the access. diff --git a/.claude/skills/packs/mendix-bulk-oql-dml/pack.yaml b/.claude/skills/packs/mendix-bulk-oql-dml/pack.yaml new file mode 100644 index 000000000..09a538866 --- /dev/null +++ b/.claude/skills/packs/mendix-bulk-oql-dml/pack.yaml @@ -0,0 +1,26 @@ +# Skill pack manifest. See docs/11-proposals/PROPOSAL_skill_packs.md. +name: mendix-bulk-oql-dml +version: 1.0.0 +description: >- + Set-based INSERT, UPDATE and DELETE through OQL statements, which the runtime + supports and Studio Pro cannot author. Three Java actions ready to apply, the + grammar matrix from sixteen probe statements, three working patterns, and the + gotcha that wrote bad data. + +# The oldest runtime that can execute any OQL DML statement. The individual +# statement forms arrived later still, and SKILL.md carries that table — +# DELETE 11.1, UPDATE 11.3 (associations 11.4), INSERT..SELECT 11.6 +# (associations 11.7), INSERT..VALUES 11.13 — because the pack is useful at +# 11.1 for a subset, and gating the whole pack at 11.13 would hide it from the +# projects that can use most of it. +min_mendix_version: 11.1.0 + +installs: + # Applying this writes three Java actions to the model, so it is never a side + # effect of copying the documentation: `mxcli skill add` copies, and only + # `--apply` executes. The file uses a `MyModule` placeholder that has to be + # replaced with the target module first. + mdl: + - mdl/oql-dml-actions.mdl + +source: https://github.com/ako/mxcli-ledger/tree/main/.claude/skills/mendix-bulk-oql-dml diff --git a/.claude/skills/packs/mendix-bulk-oql-dml/references/gotchas.md b/.claude/skills/packs/mendix-bulk-oql-dml/references/gotchas.md new file mode 100644 index 000000000..5bfa2d018 --- /dev/null +++ b/.claude/skills/packs/mendix-bulk-oql-dml/references/gotchas.md @@ -0,0 +1,133 @@ +# What goes wrong + +Symptom first, because that is what you have when you arrive. + +--- + +## Nothing is ever rejected, and the input is not that clean + +**An association compared to `null` in a WHERE matches no rows, in either +spelling, with no error.** + +A loader resolved an account name into an association, then rejected the rows +where that failed: + +```sql +update Ledger.ImportRow set IsValid = false, Problem = 'Unknown account' +where Batch = $b and Ledger.ImportRow_Account = null +``` + +Six rows in, one naming an account that does not exist. One rejection was +reported — a different row, for a different reason — and the unresolvable row was +promoted into the target table **with no account at all**. + +Three idioms, same batch, same row, counted by the statements themselves: + +``` +where Ledger.ImportRow_Account = null rows=0 +where Ledger.ImportRow/Ledger.ImportRow_Account/…/id = null rows=0 +where not exists (select 1 from Ledger.Account as a + where UPPER(a.Name) = UPPER(Ledger.ImportRow/AccountName)) + rows=1 +``` + +**Test the source, not the association.** Ask whether a matching row exists in +the table you are resolving against. With `not exists` the same batch rejects 2 +of 6 and promotes 4, which was the planted answer. + +The reason this is dangerous rather than annoying: a validation written the wrong +way passes every row it is given, and "nothing was rejected" is exactly what +clean input looks like. + +--- + +## `Duplicate column name: ID`, and there is no column called ID + +An `INSERT … SELECT` whose select list contains two association paths: + +```sql +select …, r/Ledger.ImportRow_Account/Ledger.Account/id, + r/Ledger.ImportRow_Category/Ledger.Category/id +``` + +Both arrive named `ID`. The error is raised at analysis time by +`com.mendix.datastorage.oqltree.AnalysisException` and names nothing that appears +in the statement text. + +**Alias every column in the list**, from the first version onward — not when the +second association is added, because that is when it starts failing and the error +does not say which pair collided. + +--- + +## "Member X of entity Y not found" on a column that exists + +Association columns are written **module-qualified**: + +```sql +insert into Ledger.BudgetOverride (MonthKey, Amount, Ledger.BudgetOverride_Category) +-- not: BudgetOverride_Category +``` + +The value is a path ending in `/id`, a bound `id` variable, a scalar subquery +selecting `id`, or `null`. + +--- + +## The screen still shows the old rows + +Expected, and the most common surprise. A statement does not pass through the +object cache: **no event handlers fire, no validation rules run, an object +already retrieved keeps its old values, and a client holding one is not +refreshed.** + +The fix that works with a data grid: + +1. A small non-persistent context object for the screen. +2. The grid on a **database** datasource constrained on it — + `where [Batch = $currentObject/Batch]`. +3. The action ends `commit $Context refresh;`. + +Refreshing the context re-runs the grid's query. It is the same mechanism a +master-detail screen already uses, pointed at a different problem. + +--- + +## No `substring` + +OQL has arithmetic, `UPPER`, `DATEPARSE`, `case`, `like`, `in`, `exists`, +subqueries and correlated subqueries. It has no `substring`, so anything that +needs to take a key apart has to be done by the caller — which is why "copy a +year" whose key is `'YYYY-MM'` text becomes twelve statements rather than one. + +Check what you actually need before designing around a single statement. + +--- + +## The app will not start, and the work that logged success is gone + +Only if you use the after-startup microflow as a probe harness — which is +otherwise an excellent loop, since it needs no UI and writes to the log. + +A statement that throws there does not just fail its own step: + +``` +ERROR - Core: An exception occurred while running the after-startup-action. +ERROR - M2EE: Starting Mendix Runtime failed. +``` + +The app does not start at all. And the whole action is one transaction, so a +failure at the end **rolls back everything before it** — two statements that had +already logged "copied 5" left zero rows behind. + +Probe with an action that catches its own exceptions and returns them as text +(`OQL_Try`). Never with one that throws. + +--- + +## General + +The grammar is not visible from the model and the error messages are written for +someone holding the parse tree. Run statements whose WHERE cannot match, log what +comes back, and read the errors — sixteen of them in one pass is what produced +`patterns.md`. Guessing costs a rebuild each time; probing costs one. diff --git a/.claude/skills/packs/mendix-bulk-oql-dml/references/patterns.md b/.claude/skills/packs/mendix-bulk-oql-dml/references/patterns.md new file mode 100644 index 000000000..2da8a7ea1 --- /dev/null +++ b/.claude/skills/packs/mendix-bulk-oql-dml/references/patterns.md @@ -0,0 +1,156 @@ +# Patterns + +Every statement below was run against real data. Row counts are what the +statements returned, checked against the database afterwards. + +## What the grammar accepts + +Sixteen probe statements, one pass: + +``` +update E set col = where … OK +update E as t set t.col = … OK alias +update E set Module.E_Assoc = OK +update E set col = (select … where … Module.E/Col = …) OK correlated +delete from E where … OK +insert into E (cols) values (…) OK 11.13+ +insert into E (cols) select … from … OK +where … like '%x%' | in (…) | exists (select …) | id in (select …) OK +select … ERR "Unexpected statement type READ" +$var with no setVariable ERR "No value supplied for the parameter" +``` + +Reads keep going through `Core.retrieveOQLDataTable`. These are statements. + +--- + +## First-match-wins rules, without a loop + +The loop version retrieves every unclaimed row, walks the rules for each, and +commits per match. The set version is one `UPDATE` per rule, in rule order. + +**The precedence lives in the WHERE clause.** Each statement only touches rows +that are still unclaimed, so a later rule cannot take a row an earlier rule took. +Nothing tracks "already matched" because nothing needs to: + +```sql +update Ledger.Transaction set + Ledger.Transaction_Category = $cat, + Ledger.Transaction_CategoryRule = $rule, + SignedAmount = 0 - Amount -- or 'Amount', decided before the statement +where Ledger.Transaction_Category = null + and IsMirror = false + and UPPER(Merchant) like UPPER($v) -- the rule's own predicate +``` + +Notes that transfer: + +- **The sign expression is chosen by the caller, not by the statement.** Whether + a category is income is known before the statement runs, so it goes in as text + rather than as a `case` the database evaluates per row. +- **The predicate is built per rule** — `= $v`, `like $v || '%'`, `like '%' || $v + || '%'`, `in (…)` — while the *value* stays bound. Concatenating the value into + the statement text breaks on an apostrophe and invites worse. +- One statement per rule, N rules, instead of one commit per matched row. + +--- + +## Copy a window of records + +`INSERT … SELECT`, made idempotent by clearing the target first, in the same +transaction: + +```sql +delete from Ledger.BudgetOverride where MonthKey like $y -- '2027-%' + +insert into Ledger.BudgetOverride + (MonthKey, Amount, Ledger.BudgetOverride_Category) +select $to, o.Amount, o/Ledger.BudgetOverride_Category/Ledger.Category/id +from Ledger.BudgetOverride as o +where o.MonthKey = $from +``` + +- **The association column is written by its qualified name** and read as a path + ending in `/id`. `BudgetOverride_Category` alone fails with *"Member + BudgetOverride_Category of entity Ledger.BudgetOverride not found"*. +- **Twelve statements, not one**, because `MonthKey` is `'YYYY-MM'` text and OQL + has no `substring` to build the target key from the source key. Each statement + still copies every category for its month. Verified: 5 copied, 5 again on a + second run, 5 in the database. + +If your key is a real date or an integer year, this collapses to one statement — +the arithmetic OQL does have. + +--- + +## Stage, validate, promote + +The strongest of the three, and the one worth copying wholesale. Rows land in a +loader entity, are checked **where they landed**, and only survivors are +promoted. + +**1. Land** — ordinary object creates, or a bulk insert if the source is a table. + +**2. Resolve names to associations** with a correlated subquery in `SET`: + +```sql +update Ledger.ImportRow set Ledger.ImportRow_Account = + (select a.id from Ledger.Account as a + where UPPER(a.Name) = UPPER(Ledger.ImportRow/AccountName)) +where Batch = $b +``` + +The correlation back to the row being updated is written as the fully qualified +entity path, `Ledger.ImportRow/AccountName`. + +**3. Validate — one statement per check**, each stamping a reason: + +```sql +update Ledger.ImportRow set IsValid = false, Problem = 'Amount must be positive' +where Batch = $b and IsValid = true and Amount <= 0 + +update Ledger.ImportRow set IsValid = false, Problem = 'Unknown account: ' + AccountName +where Batch = $b and IsValid = true + and not exists (select 1 from Ledger.Account as a + where UPPER(a.Name) = UPPER(Ledger.ImportRow/AccountName)) +``` + +`and IsValid = true` in every check means a row keeps the **first** reason it +failed, rather than the last — the same first-match-wins trick as the rules. + +**Do not write the resolution check as `Ledger.ImportRow_Account = null`.** It +matches nothing. See `gotchas.md`; this is the one that wrote bad data. + +**4. Promote** — one statement, every column aliased: + +```sql +insert into Ledger.Transaction + (TxDate, Merchant, Description, Amount, SignedAmount, IsMirror, + Ledger.Transaction_Account, Ledger.Transaction_Category) +select DATEPARSE(r.TxDateText, 'yyyy-MM-dd') as TxDate, + r.Merchant as Merchant, r.Description as Description, r.Amount as Amount, + case when r/Ledger.ImportRow_Category/Ledger.Category + /Ledger.Category_CategoryGroup/Ledger.CategoryGroup/GroupType = 'Income' + then r.Amount else 0 - r.Amount end as SignedAmount, + false as IsMirror, + r/Ledger.ImportRow_Account/Ledger.Account/id as AccountId, + r/Ledger.ImportRow_Category/Ledger.Category/id as CategoryId +from Ledger.ImportRow as r +where r.Batch = $b and r.IsValid = true +``` + +`DATEPARSE` turns landed text into a date inside the statement. A `case` over a +path several associations long computes the sign. Both mean the loader entity can +hold text and the target can hold types. + +**5. Clear the promoted rows, keep the rejects:** + +```sql +delete from Ledger.ImportRow where Batch = $b and IsValid = true +``` + +The rejects stay, each with its reason, which is the entire argument for a loader +table over an import that half-succeeds and reports a number. + +Verified end to end: 6 rows in, 2 rejected with reasons, 4 promoted, 932 → 936 +transactions. diff --git a/.gitignore b/.gitignore index 919bd04cf..5d64b99f1 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ mdl/grammar/parser/ *.tar *.tgz cmd/mxcli/skills/ +cmd/mxcli/skillpacks/ cmd/mxcli/commands/ cmd/mxcli/lint-rules/ cmd/mxcli/changelog.md diff --git a/Makefile b/Makefile index 97c55fce3..791843ef4 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ GO_BUILD_FLAGS = -trimpath # Clean version for VS Code extension (must be valid semver: major.minor.patch) VSCE_VERSION = $(shell echo "$(VERSION)" | sed 's/^v//; s/-.*//' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$$' || echo "0.0.0") -.PHONY: build build-debug size release clean test engine-diff test-mdl check-mdl check-skill-mdl check-widget-versions grammar completions sync-skills sync-commands sync-lint-rules sync-changelog sync-all docs documentation docs-site docs-serve vscode-ext vscode-install source-tree sbom sbom-report lint lint-go lint-ts fmt vet +.PHONY: build build-debug size release clean test engine-diff test-mdl check-mdl check-skill-mdl check-widget-versions grammar completions sync-skills sync-skill-packs sync-commands sync-lint-rules sync-changelog sync-all docs documentation docs-site docs-serve vscode-ext vscode-install source-tree sbom sbom-report lint lint-go lint-ts fmt vet # Helper: copy file only if content differs (avoids mtime updates that invalidate go build cache) # Usage: $(call copy-if-changed,src,dst) @@ -54,6 +54,14 @@ sync-skills: done; \ if [ $$changed -gt 0 ]; then echo "Synced $$changed skill file(s)"; fi +# Sync skill packs from .claude/skills/packs to cmd/mxcli/skillpacks for embedding. +# Recursive, unlike sync-skills: a pack is a directory tree and flattening it +# would silently collide same-named files in different subdirectories. +sync-skill-packs: + @mkdir -p cmd/mxcli/skillpacks + @rsync -a --delete --exclude='.DS_Store' .claude/skills/packs/ cmd/mxcli/skillpacks/ 2>/dev/null \ + || { rm -rf cmd/mxcli/skillpacks && mkdir -p cmd/mxcli/skillpacks && cp -R .claude/skills/packs/. cmd/mxcli/skillpacks/; } + # Sync commands from .claude/commands/mendix to cmd/mxcli/commands for embedding sync-commands: @mkdir -p cmd/mxcli/commands @@ -94,7 +102,7 @@ sync-changelog: $(call copy-if-changed,CHANGELOG.md,cmd/mxcli/changelog.md) # Sync skills, commands, lint rules, and changelog -sync-all: sync-skills sync-commands sync-lint-rules sync-vsix sync-changelog +sync-all: sync-skills sync-skill-packs sync-commands sync-lint-rules sync-vsix sync-changelog # Generate LSP completion items from grammar (only rewrites file if content changed) completions: @@ -218,6 +226,15 @@ check-mdl: build # ENTITY `ADD (attr)` instead of `ADD ATTRIBUTE attr: type`) can't drift into docs. check-skill-mdl: build @./scripts/check-skill-mdl.sh ./$(BUILD_DIR)/$(BINARY_NAME) .claude/skills/mendix + @./scripts/check-skill-mdl.sh ./$(BUILD_DIR)/$(BINARY_NAME) .claude/skills/packs + @# The script above checks fenced blocks in markdown. A pack also ships real + @# .mdl files, which it does not see — and a pack whose own MDL is never + @# checked is a pack that rots. + @for f in .claude/skills/packs/*/mdl/*.mdl; do \ + [ -e "$$f" ] || continue; \ + ./$(BUILD_DIR)/$(BINARY_NAME) check "$$f" >/dev/null || { echo "FAILED: $$f"; exit 1; }; \ + echo " ok $$f"; \ + done @./scripts/check-skill-mdl.sh ./$(BUILD_DIR)/$(BINARY_NAME) docs-site/src # Run integration tests (requires mx binary / mxbuild) diff --git a/cmd/mxcli/cmd_skill.go b/cmd/mxcli/cmd_skill.go new file mode 100644 index 000000000..099d9c973 --- /dev/null +++ b/cmd/mxcli/cmd_skill.go @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "fmt" + "io/fs" + "os" + "path/filepath" + + "github.com/spf13/cobra" + + "github.com/mendixlabs/mxcli/cmd/mxcli/skillpack" +) + +var skillPackDir string + +// packsFS returns the embedded packs rooted at the pack directory, so callers +// see `/SKILL.md` rather than `skillpacks//SKILL.md`. +func packsFS() (fs.FS, error) { + return fs.Sub(skillPacksFS, "skillpacks") +} + +// targetSkillsDir is where packs are installed. Packs are directory-shaped, so +// they go to .claude/skills/ (which reads a directory as a skill) rather than +// .ai-context/skills/, which is the flat prose set. +func targetSkillsDir(projectDir string) string { + if skillPackDir != "" { + return skillPackDir + } + return filepath.Join(projectDir, ".claude", "skills") +} + +var skillCmd = &cobra.Command{ + Use: "skill", + Short: "Manage skill packs — skills that carry assets, not just prose", + Long: `Manage skill packs. + +A skill pack is a directory: SKILL.md plus the references, spec templates, +scripts and MDL that go with it. Packs are opt-in — unlike the prose skills +written by ` + "`mxcli init`" + `, a pack may install a widget or apply Java actions +to the model, so nothing is installed until you ask for it.`, +} + +var skillListCmd = &cobra.Command{ + Use: "list", + Short: "List available skill packs and which are installed here", + RunE: func(cmd *cobra.Command, args []string) error { + fsys, err := packsFS() + if err != nil { + return err + } + packs, err := skillpack.List(fsys) + if err != nil { + return err + } + if len(packs) == 0 { + fmt.Println("No skill packs are bundled in this build.") + return nil + } + + dir := targetSkillsDir(".") + installed := map[string]bool{} + names, err := skillpack.Installed(dir) + if err != nil { + return err + } + for _, n := range names { + installed[n] = true + } + + for _, p := range packs { + mark := " " + if installed[p.Name] { + mark = "*" + } + fmt.Printf("%s %-24s %-8s", mark, p.Name, p.Version) + if p.MinMendixVersion != "" { + fmt.Printf(" (Mendix %s+)", p.MinMendixVersion) + } + if p.WritesToModel() { + fmt.Print(" [--apply writes to the model]") + } + fmt.Println() + } + fmt.Printf("\n* = installed in %s\n", dir) + return nil + }, +} + +var skillAddCmd = &cobra.Command{ + Use: "add ", + Short: "Install a skill pack into this project", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + fsys, err := packsFS() + if err != nil { + return err + } + pack, err := skillpack.Load(fsys, args[0]) + if err != nil { + return err + } + dir := targetSkillsDir(".") + if err := os.MkdirAll(dir, 0o755); err != nil { + return err + } + res, err := skillpack.Install(fsys, pack.Name, dir) + if err != nil { + return err + } + + switch { + case !res.Changed(): + fmt.Printf("%s is already up to date in %s\n", pack.Name, dir) + default: + fmt.Printf("Installed %s %s into %s\n", pack.Name, pack.Version, dir) + fmt.Printf(" %d file(s) written", len(res.Written)) + if len(res.Pruned) > 0 { + fmt.Printf(", %d removed (no longer shipped)", len(res.Pruned)) + } + fmt.Println() + } + + // Copying the pack never touches the model. Anything that would is + // reported as a next step the user runs deliberately — a documentation + // install that silently added Java actions to the .mpr would be exactly + // the kind of surprise this repo keeps refusing. + if pack.WritesToModel() { + fmt.Println("\nThis pack ships MDL that adds Java actions to the model. It has NOT been applied.") + for _, m := range pack.Installs.MDL { + fmt.Printf(" review then apply: mxcli exec %s -p .mpr\n", + filepath.Join(dir, pack.Name, filepath.FromSlash(m))) + } + fmt.Println(" (the MDL uses a MyModule placeholder — set the target module first)") + } + return nil + }, +} + +var skillRemoveCmd = &cobra.Command{ + Use: "remove ", + Short: "Remove an installed skill pack from this project", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + dir := targetSkillsDir(".") + removed, err := skillpack.Remove(dir, args[0]) + if err != nil { + return err + } + if !removed { + fmt.Printf("%s is not installed in %s\n", args[0], dir) + return nil + } + fmt.Printf("Removed %s from %s\n", args[0], dir) + fmt.Println("Anything the pack applied to the model (Java actions, widgets) is left alone.") + return nil + }, +} + +var skillUpgradeCmd = &cobra.Command{ + Use: "upgrade [pack]", + Short: "Re-install installed packs from this binary, pruning files they no longer ship", + Args: cobra.MaximumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + fsys, err := packsFS() + if err != nil { + return err + } + dir := targetSkillsDir(".") + names, err := skillpack.Installed(dir) + if err != nil { + return err + } + if len(args) == 1 { + names = []string{args[0]} + } + if len(names) == 0 { + fmt.Printf("No skill packs installed in %s\n", dir) + return nil + } + quiet := true + for _, n := range names { + res, err := skillpack.Install(fsys, n, dir) + if err != nil { + return err + } + if res.Changed() { + quiet = false + fmt.Printf("%s: %d written, %d pruned\n", n, len(res.Written), len(res.Pruned)) + } + } + if quiet { + // Silence is the common case and the only acceptable one, same as + // the flat-skill sync. + return nil + } + return nil + }, +} + +func init() { + skillCmd.PersistentFlags().StringVar(&skillPackDir, "dir", "", + "Install packs here instead of ./.claude/skills") + skillCmd.AddCommand(skillListCmd, skillAddCmd, skillRemoveCmd, skillUpgradeCmd) + rootCmd.AddCommand(skillCmd) +} diff --git a/cmd/mxcli/skillpack/skillpack.go b/cmd/mxcli/skillpack/skillpack.go new file mode 100644 index 000000000..03df6d8c0 --- /dev/null +++ b/cmd/mxcli/skillpack/skillpack.go @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Package skillpack installs skill packs — skills that carry more than prose. +// +// A flat skill is one Markdown file, and the machinery for those flattens +// everything to a basename. A pack is a directory: SKILL.md plus references/, +// specs/, scripts/ and mdl/. Two rules follow, and both are load-bearing: +// +// 1. Files are written by their path RELATIVE TO THE PACK ROOT, never by +// basename. Flattening does not error — it produces a plausible directory +// with files silently overwriting each other (references/install.md and +// specs/install.md collide), which is the failure mode this repo keeps +// writing down: a tool accepting what it does not implement is worse than +// one that rejects it. +// +// 2. Installing prunes files the pack no longer ships. Overwrite-without-delete +// leaves a v1 asset behind forever, and a stale spec template is worse than +// a missing one because it still looks current. +// +// The embedded FS is passed in rather than embedded here: go:embed can only +// reach files inside its own package directory, and taking an fs.FS is what +// lets the tests drive the real hazards through fstest.MapFS. +package skillpack + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path" + "path/filepath" + "sort" + "strings" + + "gopkg.in/yaml.v3" +) + +// ManifestName is the per-pack manifest, read from the pack root. +const ManifestName = "pack.yaml" + +// Manifest describes a pack. Only Name is required; everything else is optional +// so that a pack can be added before its install story is settled. +type Manifest struct { + Name string `yaml:"name"` + Version string `yaml:"version"` + Description string `yaml:"description"` + MinMendixVersion string `yaml:"min_mendix_version"` + Source string `yaml:"source"` + Installs Installs `yaml:"installs"` + Verify string `yaml:"verify"` +} + +// Installs lists what a pack does to a project beyond copying its own files. +// These are deliberately separate from the copy: `skill add` writes the pack's +// documentation and assets, and only an explicit --apply runs anything that +// touches the model. +type Installs struct { + Widgets []string `yaml:"widgets"` + MDL []string `yaml:"mdl"` +} + +// Pack is a manifest plus the directory it was read from. +type Pack struct { + Manifest + Dir string // path within the source FS +} + +// WritesToModel reports whether installing this pack fully (with --apply) would +// modify the .mpr. Callers use it to decide whether to demand confirmation. +func (p Pack) WritesToModel() bool { return len(p.Installs.MDL) > 0 } + +// List returns every pack in the FS, sorted by name. A directory without a +// readable manifest is an error rather than a skip: a pack that silently does +// not appear is indistinguishable from one that was never vendored. +func List(fsys fs.FS) ([]Pack, error) { + entries, err := fs.ReadDir(fsys, ".") + if err != nil { + return nil, fmt.Errorf("reading pack root: %w", err) + } + var packs []Pack + for _, e := range entries { + if !e.IsDir() { + continue + } + p, err := Load(fsys, e.Name()) + if err != nil { + return nil, err + } + packs = append(packs, p) + } + sort.Slice(packs, func(i, j int) bool { return packs[i].Name < packs[j].Name }) + return packs, nil +} + +// Load reads one pack's manifest. +func Load(fsys fs.FS, dir string) (Pack, error) { + raw, err := fs.ReadFile(fsys, path.Join(dir, ManifestName)) + if err != nil { + return Pack{}, fmt.Errorf("pack %q has no readable %s: %w", dir, ManifestName, err) + } + var m Manifest + if err := yaml.Unmarshal(raw, &m); err != nil { + return Pack{}, fmt.Errorf("pack %q: parsing %s: %w", dir, ManifestName, err) + } + if m.Name == "" { + return Pack{}, fmt.Errorf("pack %q: %s has no name", dir, ManifestName) + } + if m.Name != dir { + // The directory is what the user types; the manifest name is what + // everything else keys on. Letting them drift makes `skill remove + // ` unable to find what `skill add ` wrote. + return Pack{}, fmt.Errorf("pack %q: %s declares name %q; they must match", dir, ManifestName, m.Name) + } + return Pack{Manifest: m, Dir: dir}, nil +} + +// Result reports what an Install did. +type Result struct { + Pack string + Written []string // relative paths written or updated + Pruned []string // relative paths removed because the pack no longer ships them + Skipped []string // relative paths already identical +} + +// Changed reports whether anything moved on disk. +func (r Result) Changed() bool { return len(r.Written) > 0 || len(r.Pruned) > 0 } + +// Install copies one pack into destDir//, preserving the pack's +// directory structure, and removes files the pack no longer ships. +// +// destDir is the skills directory of the target project (e.g. .claude/skills). +func Install(fsys fs.FS, name, destDir string) (Result, error) { + pack, err := Load(fsys, name) + if err != nil { + return Result{}, err + } + res := Result{Pack: pack.Name} + target := filepath.Join(destDir, pack.Name) + + shipped := map[string]bool{} + + err = fs.WalkDir(fsys, pack.Dir, func(p string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + // Relative to the pack root — NOT d.Name(). See the package comment. + rel, err := filepath.Rel(pack.Dir, p) + if err != nil { + return err + } + rel = filepath.ToSlash(rel) + shipped[rel] = true + + want, err := fs.ReadFile(fsys, p) + if err != nil { + return err + } + dst := filepath.Join(target, filepath.FromSlash(rel)) + if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { + return err + } + if have, readErr := os.ReadFile(dst); readErr == nil && string(have) == string(want) { + res.Skipped = append(res.Skipped, rel) + return nil + } + if err := os.WriteFile(dst, want, 0o644); err != nil { + return err + } + res.Written = append(res.Written, rel) + return nil + }) + if err != nil { + return res, fmt.Errorf("installing pack %q: %w", name, err) + } + + pruned, err := prune(target, shipped) + if err != nil { + return res, fmt.Errorf("pruning pack %q: %w", name, err) + } + res.Pruned = pruned + + sort.Strings(res.Written) + sort.Strings(res.Skipped) + return res, nil +} + +// prune removes files under root that the pack no longer ships, then removes any +// directory left empty by that. Files the pack still ships are left alone. +func prune(root string, shipped map[string]bool) ([]string, error) { + var removed []string + err := filepath.WalkDir(root, func(p string, d fs.DirEntry, err error) error { + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return nil // nothing installed yet + } + return err + } + if d.IsDir() { + return nil + } + rel, err := filepath.Rel(root, p) + if err != nil { + return err + } + rel = filepath.ToSlash(rel) + if shipped[rel] { + return nil + } + if err := os.Remove(p); err != nil { + return err + } + removed = append(removed, rel) + return nil + }) + if err != nil { + return nil, err + } + if err := removeEmptyDirs(root); err != nil { + return nil, err + } + sort.Strings(removed) + return removed, nil +} + +// removeEmptyDirs deletes directories left empty by a prune, deepest first. The +// pack root itself is kept — an installed pack with no files is still installed, +// and removing the root would make Remove and Install disagree. +func removeEmptyDirs(root string) error { + var dirs []string + err := filepath.WalkDir(root, func(p string, d fs.DirEntry, err error) error { + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return nil + } + return err + } + if d.IsDir() && p != root { + dirs = append(dirs, p) + } + return nil + }) + if err != nil { + return err + } + // Deepest first, so a directory emptied by removing its subdirectories is + // itself removed in the same pass. + sort.Sort(sort.Reverse(sort.StringSlice(dirs))) + for _, d := range dirs { + entries, err := os.ReadDir(d) + if err != nil { + return err + } + if len(entries) == 0 { + if err := os.Remove(d); err != nil { + return err + } + } + } + return nil +} + +// Remove deletes an installed pack from destDir. +func Remove(destDir, name string) (bool, error) { + // Guard against a name that would escape the skills directory. `skill + // remove ../../etc` must not be a path traversal. + // + // The dot entries need naming explicitly: ".." survives + // `name == filepath.Base(filepath.Clean(name))`, because Base("..") is "..". + // A guard built only from that check accepts the single input that matters + // most here, and RemoveAll would then take the whole skills directory. + if name == "" || name == "." || name == ".." || + strings.ContainsRune(name, filepath.Separator) || strings.ContainsRune(name, '/') { + return false, fmt.Errorf("invalid pack name %q", name) + } + target := filepath.Join(destDir, name) + if _, err := os.Stat(target); errors.Is(err, fs.ErrNotExist) { + return false, nil + } + if err := os.RemoveAll(target); err != nil { + return false, err + } + return true, nil +} + +// Installed reports which packs are present in destDir, by directory name. +func Installed(destDir string) ([]string, error) { + entries, err := os.ReadDir(destDir) + if errors.Is(err, fs.ErrNotExist) { + return nil, nil + } + if err != nil { + return nil, err + } + var names []string + for _, e := range entries { + if !e.IsDir() { + continue + } + if _, err := os.Stat(filepath.Join(destDir, e.Name(), ManifestName)); err == nil { + names = append(names, e.Name()) + } + } + sort.Strings(names) + return names, nil +} diff --git a/cmd/mxcli/skillpack/skillpack_test.go b/cmd/mxcli/skillpack/skillpack_test.go new file mode 100644 index 000000000..050523ae9 --- /dev/null +++ b/cmd/mxcli/skillpack/skillpack_test.go @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: Apache-2.0 + +package skillpack + +import ( + "os" + "path/filepath" + "testing" + "testing/fstest" +) + +const manifest = "name: demo-pack\nversion: 1.0.0\ndescription: a demo\n" + +func demoFS() fstest.MapFS { + return fstest.MapFS{ + "demo-pack/pack.yaml": {Data: []byte(manifest)}, + "demo-pack/SKILL.md": {Data: []byte("---\nname: demo-pack\n---\n# Demo\n")}, + "demo-pack/references/install.md": {Data: []byte("REFERENCES install\n")}, + "demo-pack/specs/install.md": {Data: []byte("SPECS install\n")}, + "demo-pack/specs/bar.json": {Data: []byte(`{"mark":"bar"}`)}, + "demo-pack/scripts/check.mjs": {Data: []byte("process.exit(0)\n")}, + "demo-pack/mdl/actions.mdl": {Data: []byte("-- actions\n")}, + "demo-pack/scripts/_helper.mjs": {Data: []byte("// underscore-prefixed\n")}, + } +} + +// TestInstallPreservesStructure is the flattening hazard, and the two +// `install.md` files are the whole point: the old skill writer joins with +// d.Name(), so under it references/install.md and specs/install.md become one +// file and the loser vanishes. That does not error — it produces a plausible +// directory with a file silently missing. +func TestInstallPreservesStructure(t *testing.T) { + dest := t.TempDir() + res, err := Install(demoFS(), "demo-pack", dest) + if err != nil { + t.Fatalf("Install: %v", err) + } + + want := map[string]string{ + "pack.yaml": manifest, + "SKILL.md": "---\nname: demo-pack\n---\n# Demo\n", + "references/install.md": "REFERENCES install\n", + "specs/install.md": "SPECS install\n", + "specs/bar.json": `{"mark":"bar"}`, + "scripts/check.mjs": "process.exit(0)\n", + "scripts/_helper.mjs": "// underscore-prefixed\n", + "mdl/actions.mdl": "-- actions\n", + } + for rel, content := range want { + got, err := os.ReadFile(filepath.Join(dest, "demo-pack", filepath.FromSlash(rel))) + if err != nil { + t.Errorf("%s: %v", rel, err) + continue + } + if string(got) != content { + t.Errorf("%s = %q, want %q", rel, got, content) + } + } + if len(res.Written) != len(want) { + t.Errorf("wrote %d files (%v), want %d", len(res.Written), res.Written, len(want)) + } +} + +// TestInstallIsIdempotent — a second install with nothing changed must write +// nothing. `mxcli init --sync-skills` runs on every session start, so churn here +// shows up as a dirty working tree on every boot. +func TestInstallIsIdempotent(t *testing.T) { + dest := t.TempDir() + if _, err := Install(demoFS(), "demo-pack", dest); err != nil { + t.Fatalf("first Install: %v", err) + } + res, err := Install(demoFS(), "demo-pack", dest) + if err != nil { + t.Fatalf("second Install: %v", err) + } + if res.Changed() { + t.Errorf("second install changed things: written=%v pruned=%v", res.Written, res.Pruned) + } +} + +// TestInstallPrunesDroppedFiles is the stale-asset hazard. A pack that drops a +// spec in v2 must not leave v1's behind — a stale spec template is worse than a +// missing one, because it still looks current. +func TestInstallPrunesDroppedFiles(t *testing.T) { + dest := t.TempDir() + if _, err := Install(demoFS(), "demo-pack", dest); err != nil { + t.Fatalf("v1 Install: %v", err) + } + + v2 := demoFS() + delete(v2, "demo-pack/specs/bar.json") + delete(v2, "demo-pack/scripts/check.mjs") + delete(v2, "demo-pack/scripts/_helper.mjs") // empties scripts/ entirely + + res, err := Install(v2, "demo-pack", dest) + if err != nil { + t.Fatalf("v2 Install: %v", err) + } + + for _, gone := range []string{"specs/bar.json", "scripts/check.mjs"} { + if _, err := os.Stat(filepath.Join(dest, "demo-pack", filepath.FromSlash(gone))); err == nil { + t.Errorf("%s survived a pack that no longer ships it", gone) + } + } + if len(res.Pruned) != 3 { + t.Errorf("pruned %v, want 3 files", res.Pruned) + } + // A directory emptied by the prune goes too, but the pack root stays. + if _, err := os.Stat(filepath.Join(dest, "demo-pack", "scripts")); err == nil { + t.Error("scripts/ was left behind empty") + } + if _, err := os.Stat(filepath.Join(dest, "demo-pack", "SKILL.md")); err != nil { + t.Errorf("still-shipped file was pruned: %v", err) + } +} + +// TestPruneLeavesUnrelatedPacksAlone — installing one pack must not reach into +// another's directory. +func TestPruneLeavesUnrelatedPacksAlone(t *testing.T) { + dest := t.TempDir() + other := filepath.Join(dest, "other-pack") + if err := os.MkdirAll(other, 0o755); err != nil { + t.Fatal(err) + } + keep := filepath.Join(other, "SKILL.md") + if err := os.WriteFile(keep, []byte("other"), 0o644); err != nil { + t.Fatal(err) + } + if _, err := Install(demoFS(), "demo-pack", dest); err != nil { + t.Fatalf("Install: %v", err) + } + if _, err := os.Stat(keep); err != nil { + t.Errorf("installing demo-pack disturbed other-pack: %v", err) + } +} + +// TestLoadRejectsNameMismatch — the directory is what the user types and the +// manifest name is what everything else keys on. If they drift, `skill remove +// ` cannot find what `skill add ` wrote. +func TestLoadRejectsNameMismatch(t *testing.T) { + fsys := fstest.MapFS{ + "demo-pack/pack.yaml": {Data: []byte("name: something-else\n")}, + } + if _, err := Load(fsys, "demo-pack"); err == nil { + t.Error("a manifest naming a different pack was accepted") + } +} + +// TestListRequiresAManifest — a directory without one is an error, not a skip. +// A pack that silently fails to appear is indistinguishable from one that was +// never vendored, which is a bad half-hour for whoever added it. +func TestListRequiresAManifest(t *testing.T) { + fsys := fstest.MapFS{ + "good/pack.yaml": {Data: []byte("name: good\n")}, + "bad/SKILL.md": {Data: []byte("# no manifest\n")}, + } + if _, err := List(fsys); err == nil { + t.Error("a directory with no pack.yaml was silently skipped") + } +} + +// TestRemoveRejectsTraversal — the pack name reaches the filesystem, so +// `skill remove ../../something` must not escape the skills directory. +func TestRemoveRejectsTraversal(t *testing.T) { + dest := t.TempDir() + for _, bad := range []string{"..", "../evil", "a/b", ""} { + if _, err := Remove(dest, bad); err == nil { + t.Errorf("Remove(%q) was accepted", bad) + } + } +} + +// TestWritesToModelFlagsMDLInstalls — copying documentation must never be +// confused with writing Java actions into the .mpr. +func TestWritesToModelFlagsMDLInstalls(t *testing.T) { + docsOnly := Pack{Manifest: Manifest{Name: "a"}} + if docsOnly.WritesToModel() { + t.Error("a docs-only pack claims it writes to the model") + } + withMDL := Pack{Manifest: Manifest{Name: "b", Installs: Installs{MDL: []string{"mdl/x.mdl"}}}} + if !withMDL.WritesToModel() { + t.Error("a pack with installs.mdl does not report writing to the model") + } +} diff --git a/cmd/mxcli/skills_content.go b/cmd/mxcli/skills_content.go index 3a1f8a9b5..7751e51dd 100644 --- a/cmd/mxcli/skills_content.go +++ b/cmd/mxcli/skills_content.go @@ -21,6 +21,18 @@ import ( //go:embed skills/*.md var skillsFS embed.FS +// Embed skill packs from the synced directory — skills that carry assets, not +// just prose (references/, specs/, scripts/, mdl/). +// +// `all:` is load-bearing rather than defensive. A plain go:embed of a directory +// skips `_`- and `.`-prefixed files, and cmd/mxcli/theme/assets.go carries the +// same prefix for exactly this reason: `_partial.scss` is how SCSS spells a +// partial, and the theme package lost them once already. A pack is just as +// likely to ship a `_helper.mjs` or an `.eslintrc`. +// +//go:embed all:skillpacks +var skillPacksFS embed.FS + // Embed all command files from the synced directory // //go:embed commands/*.md diff --git a/docs/11-proposals/PROPOSAL_skill_packs.md b/docs/11-proposals/PROPOSAL_skill_packs.md new file mode 100644 index 000000000..0a3c2cf3a --- /dev/null +++ b/docs/11-proposals/PROPOSAL_skill_packs.md @@ -0,0 +1,187 @@ +--- +title: Skill packs — shipping a skill that carries more than prose +status: proposed +date: 2026-08-15 +related: + - cmd/mxcli/skills_content.go + - cmd/mxcli/init.go + - cmd/mxcli/init_skills_sync.go + - cmd/mxcli/theme/assets.go + - docs/13-decisions/0005-semantic-model-interface-currency.md +--- + +# Skill packs — shipping a skill that carries more than prose + +## Problem + +mxcli ships 65 skills today and every one of them is a single Markdown file. That +was never a design decision; it is what the mechanism can carry. + +The [mxcli-ledger](https://github.com/ako/mxcli-ledger) project has produced two +blocks that do not fit: + +| Pack | Carries | +|---|---| +| `mendix-vega-charts` | `SKILL.md`, 3 `references/*.md`, 7 spec templates with sample data (`specs/*.json`), a headless checker (`scripts/check-spec.mjs` + `package.json`) | +| `mendix-bulk-oql-dml` | `SKILL.md`, 2 `references/*.md`, an MDL file applying three Java actions (`mdl/oql-dml-actions.mdl`) | + +Both exist for the same reason: the block is **easier for a coding agent than for +a person in Studio Pro** — a large JSON specification, a Java action that has to +be written and compiled. The agent absorbs the awkward part once, and the pack is +what stops it rediscovering the awkward part every time. + +A third is wanted (`mendix-odata-pushdown`, Java actions that push `$filter` / +`$orderby` / `$top` / `$skip` into database-connector SQL) and there will be more. +Skills that carry assets is the general shape, not a special case for these two. + +### mxcli cannot ship any of it + +Four independent places block it, and each fails differently: + +| Layer | Today | Failure | +|---|---|---| +| Embed | `//go:embed skills/*.md` | Flat glob, `.md` only. `.json`, `.mjs`, `.mdl` and subdirectories are not in the binary at all | +| Sync | `for f in .claude/skills/mendix/*.md` (Makefile) | Flat copy; nothing below the top level is seen | +| Write | 3 loops, `filepath.Join(dir, d.Name())` | **Basename.** Nesting is flattened, so `references/install.md` and `specs/install.md` would silently overwrite each other | +| Refresh | `syncAIContextSkills`: `if e.IsDir() { continue }` | Directory skills are skipped by `--sync-skills`, so a pack would never follow a binary upgrade — the bug fixed for flat skills in mxcli-todo #114 | + +The write layer is the one to watch. It does not error on a nested pack; it +produces a plausible-looking directory with files missing or overwritten. That is +the failure mode this repo keeps meeting and keeps writing down: *the tool +accepting something it does not implement is worse than rejecting it*, because +every check comes back green. + +## What a pack is, and why it is not just a bigger skill + +**Packs are opt-in; skills are not.** This is the load-bearing distinction and it +belongs in the mechanism rather than in documentation. + +The 65 current skills are pure prose. Writing them into every project is free and +reversible — worst case an agent reads a page it did not need. + +A pack is not free: + +- `mendix-vega-charts` requires **installing a custom pluggable widget** into the + project, and re-namespacing it away from the ledger's `ledger.widget.web.*`. +- `mendix-bulk-oql-dml` **applies three Java actions to the model** via MDL, which + is a model write with a build cost and a review surface. + +Writing either into every `mxcli init` would be wrong. So a pack is an +**installable unit with a manifest**, and `mxcli init` keeps shipping exactly the +prose skills it ships today unless asked otherwise. + +## Design + +### Source of truth: vendored + +Packs live in the mxcli repo and ship inside the binary, the same as skills, +commands and lint rules do now. Versioned with the binary, works offline, no +trust or caching questions. A third-party pack arrives as a PR. + +A fetch/registry model was considered and rejected **for now**: it adds network, +provenance and cache-invalidation concerns to solve a problem nobody has yet +(there are three packs, all in-house). The manifest below is deliberately +sufficient for a fetched pack, so this does not foreclose it. + +### Layout + +``` +.claude/skills/packs// source of truth, edited here + pack.yaml manifest + SKILL.md frontmatter name + description + references/*.md loaded on demand + specs/*.json scripts/* mdl/*.mdl assets +``` + +Synced by `make sync-skill-packs` into `cmd/mxcli/skillpacks/` for embedding, the +same build-time flow the flat skills already use. **Edit the source, never the +embed dir.** + +### The four mechanism changes + +1. **`//go:embed all:skillpacks`.** The `all:` prefix is load-bearing, not + defensive: a plain `go:embed` of a directory skips `_`- and `.`-prefixed files. + `cmd/mxcli/theme/assets.go` carries the same prefix for exactly this reason — + `_partial.scss` is how SCSS spells a partial, and the theme package lost them + once already. A pack is just as likely to carry a `_helper.mjs` or a + `.eslintrc`. + +2. **Recursive sync.** `cp -R` preserving structure, with the existing + `copy-if-changed` discipline so unchanged files do not invalidate the build + cache. + +3. **Write by relative path.** Strip the embed root, keep the remainder, create + parents. The three near-duplicate walk loops in `init.go` (`.ai-context/`, + `.opencode/`, `.vibe/`) collapse into one projector with per-agent targets; + they have already drifted apart once. + +4. **Prune on refresh.** Today's sync overwrites but never deletes. A pack that + drops an asset in v2 would leave the v1 file behind forever, and a stale spec + template is worse than a missing one because it looks current. + +### Local edits are refused, not overwritten + +A pack writes files into a project the user then owns. `theme/` solved this +already: generated regions are digest-fenced, and a block carrying local edits is +**refused rather than overwritten** — guard-don't-drop, the same principle as +[ADR-0005](../13-decisions/0005-semantic-model-interface-currency.md). + +Packs reuse it. `mxcli skill upgrade` reports what it refused and why; it never +silently reverts a spec the user tuned. + +### Manifest + +```yaml +name: mendix-vega-charts +version: 1.0.0 +description: ... # mirrors SKILL.md frontmatter +min_mendix_version: 10.18.0 # gates on the project, per sdk/versions/*.yaml +installs: + widgets: [VegaChart.mpk] # copied into widgets/ + mdl: [mdl/oql-dml-actions.mdl] # applied to the model, requires --apply +verify: scripts/check-spec.mjs # exit code, runnable in seconds +``` + +`min_mendix_version` uses the existing version registry rather than inventing a +second gate. `installs.mdl` is the part that writes to the model, so it is +explicit and separately confirmable — installing a pack must never modify the +model as a side effect of copying documentation. + +### Commands + +``` +mxcli skill list # available packs + what is installed here +mxcli skill add [--apply] # copy assets; --apply runs installs.mdl +mxcli skill remove +mxcli skill upgrade [] # re-sync, prune, refuse locally-edited files +mxcli init --with [,] # at project creation +``` + +`mxcli init` with no `--with` behaves exactly as today. + +## What this does not solve + +**Project-neutrality of the ledger's packs.** Both reference `Ledger.*` entity +names, and `mendix-vega-charts` ships a widget under `ledger.widget.web.vegachart` +with re-namespacing steps written out in `references/install.md`. Vendoring them +as-is would hand every project the ledger's namespace. Either the widget is +re-published under a neutral namespace before it is vendored, or the rename is +automated as part of `skill add`. This is a prerequisite for the vega pack +specifically, not for the mechanism. + +**Verifying a pack in CI.** `mendix-vega-charts` ships a Node checker and seven +specs; `mendix-bulk-oql-dml` ships an MDL file that `make check-skill-mdl` should +be checking. Neither runs today. A pack whose own verifier is not run in CI is a +pack that rots. + +## Plan + +| Slice | Content | +|---|---| +| 1 | Embed + recursive sync + relative-path write + prune. One pack fixture, no CLI surface. Proves the mechanism carries nested non-Markdown assets through `init`. | +| 2 | `pack.yaml`, version gating, `mxcli skill list/add/remove/upgrade`, digest-fenced local-edit refusal. | +| 3 | Vendor `mendix-bulk-oql-dml` (no widget, so no namespace question), wire its MDL into `make check-skill-mdl`. | +| 4 | Vendor `mendix-vega-charts` once the widget namespace is settled; run `check-spec.mjs` over the shipped specs in CI. | + +Slice 1 is worth landing on its own: it removes the silent-flattening hazard in +the write path whether or not any pack ever ships. From bf2821397e88e76bd07d9749e06f383959966260 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Aug 2026 08:50:26 +0000 Subject: [PATCH 06/22] Add the Vega charting pack, with the namespace fitted at install MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The second ledger pack was deferred in the first commit because its widget ships under `ledger.widget.web.*`, and vendoring it as-is would hand every project the ledger's namespace. This adds it, and the mechanism that makes that safe. A pluggable widget's id is its identity. Two apps whose widgets share one are two apps claiming the same widget, and the symptom is not a build error — it is a widget resolving to somebody else's build. The ledger's own install notes make this three manual edits across three files, done before the build, "otherwise every page that carries the widget has to be re-applied". So the widget source ships with placeholders and `skill add` substitutes the destination project's namespace into package.json (packagePath and the build's projectPath), src/package.xml (the client-module path) and src/VegaChart.xml (the id) — from one value, so they cannot drift apart. `--namespace acme` overrides; the default is derived from the project name and always printed, because a namespace nobody chose is as wrong as one that does not fit. Three properties make a missed substitution impossible rather than unlikely: - Placeholders, not a real namespace. Leaving `ledger` in place means a bug ships THEIR namespace silently; an unsubstituted {{NAMESPACE}} fails loudly. - A whitelist, not a scan. Only files named in rewrite.files are touched. A pack ships megabytes of built JS and spec JSON, and a blind replace is how a spec containing brace syntax quietly becomes something else. - Drift either way is an error — a declared file carrying no token (the file changed under the manifest) and a declared file the pack does not ship both refuse the install. `skill upgrade` re-substitutes what the install recorded in pack.lock.yaml rather than re-deriving. Re-deriving would change the id when a project is renamed, and a changed widget id is every page pointing at a widget that no longer exists under that name. The lock is written by the install, not shipped by the pack, so the prune had to learn to keep it. The widget ships as SOURCE, not a built .mpk. The built package is 3.1 MB of bundled Vega, which has no business in a source repo or in the binary; and the namespace has to be right BEFORE the build, so shipping a prebuilt package would mean rewriting paths inside a zip and hoping, where rewriting source is the path the ledger actually verified. Verified end to end on Mendix 11.12.1, not just at the unit level: mxcli skill add mendix-vega-charts -p App1112.mpr -> namespace app1112 npm ci && npm run build -> app1112.widget.web.VegaChart.mpk mxcli widget init -p App1112.mpr -> discovered a page carrying the widget, then mx check -> 0 errors Every path inside the built package is under the new namespace, as is the id in VegaChart.xml, and zero paths carry the old one. The build's .mpk landed straight in the project's widgets/ — which is how the relative projectPath got caught: filepath.Rel refuses to mix a relative and an absolute path, and the fallback baked an ABSOLUTE path into a package.json that gets committed, working on exactly one machine. `mxcli widget init` is now named in the install output. Without it the first page fails with "no definition for widget ...", which reads as a packaging problem rather than a step nobody mentioned. TestVendoredPacks* check the packs that actually ship, not fixtures: every rewrite.files entry exists and carries a token, everything installs.* names is present, and no widget source carries a harvested project's namespace. Both guards were confirmed by vendoring a bad pack on purpose. The pack's own install.md described the three manual edits; it now describes what mxcli does instead, since a doc contradicting the tool is worse than no doc. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4 --- .claude/skills/packs/README.md | 9 +- .../skills/packs/mendix-vega-charts/SKILL.md | 144 ++ .../skills/packs/mendix-vega-charts/pack.yaml | 39 + .../references/failure-modes.md | 154 ++ .../mendix-vega-charts/references/install.md | 101 + .../references/properties.md | 82 + .../mendix-vega-charts/scripts/check-spec.mjs | 119 + .../mendix-vega-charts/scripts/package.json | 13 + .../packs/mendix-vega-charts/specs/README.md | 45 + .../specs/bar-ranked.data.json | 3 + .../mendix-vega-charts/specs/bar-ranked.json | 18 + .../specs/calendar-heatmap.data.json | 642 ++++++ .../specs/calendar-heatmap.json | 24 + .../specs/line-timeseries.data.json | 202 ++ .../specs/line-timeseries.json | 32 + .../specs/scatter-brushed.data.json | 842 +++++++ .../specs/scatter-brushed.json | 27 + .../specs/small-multiples-table.data.json | 1958 +++++++++++++++++ .../specs/small-multiples-table.json | 66 + .../specs/sparkline-cell.data.json | 86 + .../specs/sparkline-cell.json | 39 + .../specs/stacked-area-by-group.data.json | 842 +++++++ .../specs/stacked-area-by-group.json | 28 + .../mendix-vega-charts/widget/package.json | 28 + .../widget/src/VegaChart.tsx | 163 ++ .../widget/src/VegaChart.xml | 60 + .../mendix-vega-charts/widget/src/package.xml | 11 + .../widget/src/ui/VegaChart.css | 36 + .../mendix-vega-charts/widget/tsconfig.json | 11 + .../widget/typings/VegaChartProps.d.ts | 45 + cmd/mxcli/cmd_skill.go | 89 +- cmd/mxcli/skillpack/namespace.go | 73 + cmd/mxcli/skillpack/rewrite_test.go | 211 ++ cmd/mxcli/skillpack/skillpack.go | 162 ++ cmd/mxcli/skillpacks_test.go | 108 + docs/11-proposals/PROPOSAL_skill_packs.md | 54 +- 36 files changed, 6553 insertions(+), 13 deletions(-) create mode 100644 .claude/skills/packs/mendix-vega-charts/SKILL.md create mode 100644 .claude/skills/packs/mendix-vega-charts/pack.yaml create mode 100644 .claude/skills/packs/mendix-vega-charts/references/failure-modes.md create mode 100644 .claude/skills/packs/mendix-vega-charts/references/install.md create mode 100644 .claude/skills/packs/mendix-vega-charts/references/properties.md create mode 100644 .claude/skills/packs/mendix-vega-charts/scripts/check-spec.mjs create mode 100644 .claude/skills/packs/mendix-vega-charts/scripts/package.json create mode 100644 .claude/skills/packs/mendix-vega-charts/specs/README.md create mode 100644 .claude/skills/packs/mendix-vega-charts/specs/bar-ranked.data.json create mode 100644 .claude/skills/packs/mendix-vega-charts/specs/bar-ranked.json create mode 100644 .claude/skills/packs/mendix-vega-charts/specs/calendar-heatmap.data.json create mode 100644 .claude/skills/packs/mendix-vega-charts/specs/calendar-heatmap.json create mode 100644 .claude/skills/packs/mendix-vega-charts/specs/line-timeseries.data.json create mode 100644 .claude/skills/packs/mendix-vega-charts/specs/line-timeseries.json create mode 100644 .claude/skills/packs/mendix-vega-charts/specs/scatter-brushed.data.json create mode 100644 .claude/skills/packs/mendix-vega-charts/specs/scatter-brushed.json create mode 100644 .claude/skills/packs/mendix-vega-charts/specs/small-multiples-table.data.json create mode 100644 .claude/skills/packs/mendix-vega-charts/specs/small-multiples-table.json create mode 100644 .claude/skills/packs/mendix-vega-charts/specs/sparkline-cell.data.json create mode 100644 .claude/skills/packs/mendix-vega-charts/specs/sparkline-cell.json create mode 100644 .claude/skills/packs/mendix-vega-charts/specs/stacked-area-by-group.data.json create mode 100644 .claude/skills/packs/mendix-vega-charts/specs/stacked-area-by-group.json create mode 100644 .claude/skills/packs/mendix-vega-charts/widget/package.json create mode 100644 .claude/skills/packs/mendix-vega-charts/widget/src/VegaChart.tsx create mode 100644 .claude/skills/packs/mendix-vega-charts/widget/src/VegaChart.xml create mode 100644 .claude/skills/packs/mendix-vega-charts/widget/src/package.xml create mode 100644 .claude/skills/packs/mendix-vega-charts/widget/src/ui/VegaChart.css create mode 100644 .claude/skills/packs/mendix-vega-charts/widget/tsconfig.json create mode 100644 .claude/skills/packs/mendix-vega-charts/widget/typings/VegaChartProps.d.ts create mode 100644 cmd/mxcli/skillpack/namespace.go create mode 100644 cmd/mxcli/skillpack/rewrite_test.go create mode 100644 cmd/mxcli/skillpacks_test.go diff --git a/.claude/skills/packs/README.md b/.claude/skills/packs/README.md index 684d68cfe..fd6291997 100644 --- a/.claude/skills/packs/README.md +++ b/.claude/skills/packs/README.md @@ -40,8 +40,13 @@ deliberately. code, runnable in seconds. 5. **Failure modes, symptoms first.** Every entry one that actually happened. 6. **Keep it project-neutral.** A pack carrying one project's module or widget - namespace hands that namespace to everyone who installs it. Use a placeholder - (`MyModule`) or automate the rename. + namespace hands that namespace to everyone who installs it. + + For a **widget**, ship the source with `{{NAMESPACE}}` / `{{NAMESPACE_PATH}}` + placeholders and list the files under `rewrite.files`; `mxcli skill add` + substitutes the destination project's namespace, and `TestVendoredPacks*` + fails the build if a real one is left in. For **MDL**, use a placeholder + module name (`MyModule`) the user replaces. 7. **Any `mdl/*.mdl` is checked by `make check-skill-mdl`.** A pack whose own MDL is never checked is a pack that rots. diff --git a/.claude/skills/packs/mendix-vega-charts/SKILL.md b/.claude/skills/packs/mendix-vega-charts/SKILL.md new file mode 100644 index 000000000..26e31a3fa --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/SKILL.md @@ -0,0 +1,144 @@ +--- +name: mendix-vega-charts +description: Chart a Mendix app with Vega-Lite through a pluggable widget that takes the specification and the data as separate properties, so the model emits rows and never assembles a chart payload. Use when a Mendix project needs charts Studio Pro's own widgets do not cover — small multiples, faceted tables, sparklines inside a data grid, calendar heatmaps, brushed scatter plots, stream graphs — or when an agent is authoring charts from MDL rather than by hand. +--- + +# Vega-Lite charts in Mendix + +## What this is + +A pluggable widget, roughly 150 lines of TSX, with two properties that matter: + +| Property | What it carries | +|---|---| +| `spec` | A Vega-Lite or Vega specification, as JSON text. Static, authored once, committed. | +| `chartData` | A string attribute holding a JSON array of row objects. Built by a microflow. | + +The widget folds the data into the spec under the name given by `datasetName` and hands the result to `vega-embed`, which picks Vega or Vega-Lite from the spec's own `$schema`. Nothing else happens at runtime. + +**The split is the whole point.** The model emits a table of facts; the specification decides what that table looks like. A microflow that concatenates a chart payload — series, axes, colours — is a microflow that has to be edited every time the chart changes, and it cannot be checked without running the app. A microflow that emits `[{"cat":"Groceries","m":"2026-06-01","v":697.70}, ...]` can be checked against the database with a SQL query, and the spec beside it can be compiled and measured without a browser at all (see [Verifying](#verifying-without-running-the-app)). + +## Who this is for + +An agent. The tradeoff is deliberate: a hand-authored Vega-Lite spec is a large piece of JSON, which is uncomfortable to maintain in Studio Pro's property editor and comfortable for a coding agent that can compile it, render it headless, and measure the result. If a human will maintain the chart by hand in Studio Pro, use Studio Pro's chart widgets instead. + +What makes this usable without deep Vega-Lite knowledge is [`specs/`](specs/) — working specifications for the common shapes, each with a sample data file, each of which compiles and renders. Start from the closest one and change fields, not structure. + +## Getting the widget into a project + +See [`references/install.md`](references/install.md). Summary: copy the widget source, `npm ci`, `npm run build`, put the built `.mpk` in the project's `widgets/` folder, and **commit it** — a gitignored `widgets/` makes every other clone unbuildable. + +Re-namespace it away from whoever built it first (`ledger.widget.web.…` here) with three edits, listed in that file. Verified: after the three edits the built package carries the new namespace throughout, including the widget id inside `VegaChart.xml`. + +## Using it from MDL + +``` +pluggablewidget 'acme.widget.web.vegachart.VegaChart' chartSpend ( + chartData: ChartData, + datasetName: 'table', + chartHeight: 0, + renderer: 'svg', + showActions: false, + spec: '{ ... }') +``` + +It needs an entity context — put it in a `dataview` over the object whose attribute holds the data. The full property table, the escaping rules for putting JSON inside an MDL string, and the click-back path are in [`references/properties.md`](references/properties.md). + +Two rules worth carrying in your head: + +- **`chartHeight: 0` means "as tall as it renders".** A chart whose height is decided by its data — a facet row per category, a legend entry per series — has no height the page can be told in advance, and a fixed container silently stops matching the moment the data grows. +- **Single quotes inside the spec must be doubled.** MDL strings are single-quoted, so a Vega expression like `['Jan','Feb'][datum.m-1]` is written `[''Jan'',''Feb''][datum.m-1]`. It is stored unescaped. + +## Two ways to get data in + +**As an attribute (the default).** A microflow builds a JSON array into a string +attribute, `chartData` binds to it, and the widget folds it into the spec. Nothing +is fetched; the payload arrives with the page. + +**As a URL.** Leave `chartData` unbound and put the address in the spec: + +```json +"data": { + "url": "/odata/chartapi/v1/MonthCategory?$filter=Yr eq 2026", + "format": {"type": "json", "property": "value"} +} +``` + +The widget needs no change for this — with no data bound it passes the spec +through untouched and Vega's own loader does the fetch. Verified end to end +against an endpoint served by the app itself: one `200`, six marks, no error, and +`format.property` unwrapping the `{"value": […]}` envelope OData returns. + +Same-origin requests carry the session cookie, so an endpoint authenticated by +session is reachable from a chart on a page of the same app without any token +handling. + +### Which to use + +The URL form buys: browser caching, a payload that is not part of the page state, +query parameters (`$filter`, `$top`) as the chart's own controls, and one endpoint +serving several charts. + +It costs: + +- **A second round trip**, after the page has already rendered. +- **The endpoint is API surface.** It is reachable by anything holding a session, + not just by the chart, so its own security rules have to be right — a chart + cannot restrict what a URL returns. +- **Rows, not aggregates,** unless the endpoint aggregates. A feed over a + transaction table sends every row and lets Vega sum them client-side, which is + fine at hundreds and not at hundreds of thousands. Publishing an OQL **view + entity** is what keeps the aggregation in the database. +- **Paging is silent.** An OData feed returns its page size and a `nextLink`; + Vega fetches once. A chart over a paged endpoint quietly plots the first page, + so cap the result deliberately (`$top`) rather than discovering the cap. +- **`check-spec.mjs` cannot fetch it.** Keep a sample `.data.json` beside the spec + so it stays checkable offline. +- **Publishing the endpoint may be the hard part.** Mendix supports publishing a + view entity keyed on selected attributes, but an OData service authored purely + in MDL could not be built here: the service's *association representation* + defaults to "associated object ID", CE7375 then demands the entity's own `ID` + as key, and that representation is not a property MDL can set (FINDINGS 113). + Setting it once in Studio Pro unblocks it; published REST avoids it entirely. + Confirm you can publish before designing a chart around a URL. + +Default to the attribute for anything a microflow already computes — it keeps the +figures checkable against SQL and the chart working with no endpoint to secure. +Reach for the URL when the data is genuinely shared, already published, or large +enough that caching matters. + +## The data side (attribute form) + +The microflow emits JSON and nothing else. Build it as a string concatenation over a retrieve, ideally over an **OQL view entity** so the aggregation happens in the database: + +``` +loop $R in $Rows +begin + set $Json = $Json + $Sep + + '{"cat":"' + $R/CategoryName + '"' + + ',"m":"' + $Month + '"' + + ',"v":' + formatDecimal($R/Total, '0.00') + '}'; + set $Sep = ','; +end loop; +``` + +`formatDecimal(x, '0.00')` is the right way to write a number into JSON — it emits a plain decimal with no grouping separators. Never write a value that could be empty into an unquoted position; emit `null` instead, and never emit `0` for "no data" (a zero against a full budget reads as maximally under budget, which is a lie the chart tells convincingly). + +## Verifying without running the app + +`scripts/check-spec.mjs` compiles a spec with sample rows, renders it headless, and reports size, mark counts and any Vega-Lite warnings. It catches most authoring errors in about a second, without a build or a browser: + +```bash +cd .claude/skills/mendix-vega-charts/scripts +npm install # vega + vega-lite, once +node check-spec.mjs ../specs/line-timeseries.json +node check-spec.mjs ../specs/*.json # all of them +``` + +Use it for more than pass/fail. Because it exposes the scenegraph, it answers questions a screenshot cannot: how tall does this get with 15 categories rather than 13, do these facet rows share a pitch, where did that band edge actually land. Several of the failure modes below were only ever settled by measuring the scenegraph. + +## When a chart looks wrong + +Read [`references/failure-modes.md`](references/failure-modes.md) **before** guessing. It catalogues the ones that cost real time on this project, each with the symptom, the cause and the fix — a tooltip that silently un-aggregates the chart it is attached to, facet rows that drift out of alignment, a fixed container that stops matching its chart, `DESCRIBE PAGE` output that will not round-trip, and a stylesheet that never reaches the bundle. + +The general rule from all of them: **measure the rendered output, do not reason about the spec.** More than once here the first hypothesis was wrong and the measurement was decisive in one command. diff --git a/.claude/skills/packs/mendix-vega-charts/pack.yaml b/.claude/skills/packs/mendix-vega-charts/pack.yaml new file mode 100644 index 000000000..a9e856afe --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/pack.yaml @@ -0,0 +1,39 @@ +# Skill pack manifest. See docs/11-proposals/PROPOSAL_skill_packs.md. +name: mendix-vega-charts +version: 1.0.0 +description: >- + Vega-Lite charting through a pluggable widget that takes the specification and + the data as separate properties, so the model emits rows and never assembles a + chart payload. Seven working spec templates with sample data, a headless spec + checker, and a catalogue of the failure modes that cost real time. + +# vega-embed needs a browser the Mendix 10.18+ client provides. The widget is +# built with @mendix/pluggable-widgets-tools 11.x; older projects need the tools +# version matched to their own major, which SKILL.md spells out. +min_mendix_version: 10.18.0 + +# The widget id carries whoever built it first, so the source ships with the +# namespace as a placeholder and `mxcli skill add` substitutes the destination +# project's. Only these files are touched — the specs and the built bundles are +# left exactly as they are. +# +# Tokens: {{NAMESPACE}}, {{NAMESPACE_PATH}}, {{PROJECT_PATH}}. +rewrite: + files: + - widget/package.json + - widget/src/package.xml + - widget/src/VegaChart.xml + +installs: + # The widget is shipped as SOURCE, not as a built .mpk. Two reasons: the built + # package is 3.1 MB of bundled Vega, which has no business in a source repo or + # in the mxcli binary; and the namespace has to be right BEFORE the build, so + # shipping a prebuilt package would mean rewriting paths inside a zip and + # hoping — where rewriting source is the path the ledger actually verified end + # to end. `npm run build` in the installed widget/ directory produces the .mpk. + widgets: + - widget + +verify: scripts/check-spec.mjs + +source: https://github.com/ako/mxcli-ledger/tree/main/.claude/skills/mendix-vega-charts diff --git a/.claude/skills/packs/mendix-vega-charts/references/failure-modes.md b/.claude/skills/packs/mendix-vega-charts/references/failure-modes.md new file mode 100644 index 000000000..b83b99789 --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/references/failure-modes.md @@ -0,0 +1,154 @@ +# When the chart looks wrong + +Each of these cost real time on a real project. Symptom first, because that is what you +have when you arrive. + +--- + +## The chart is empty and nothing errored + +**Cause A — the spec uses `"width": "container"` and the stylesheet is missing.** The +container measures zero, so the chart draws at zero. An empty chart is not an error. +Check that `VegaChart.tsx` still imports `./ui/VegaChart.css`. + +**Cause B — `datasetName` does not match the spec.** The widget injects +`datasets: {: rows}`; the spec must say `{"data": {"name": ""}}`. +A mismatch leaves the spec's own (absent) data in place. No error either. + +**Cause C — the data attribute is empty.** Check the microflow actually wrote it; an +empty string parses as nothing and the widget renders an empty chart rather than an +error. + +--- + +## Numbers in the chart are smaller than the numbers in the database + +**A field named in a tooltip without an aggregate becomes part of the group-by.** This is +the nastiest one here, because the chart still looks plausible and the tooltip is +*consistent with what it drew*. + +```json +{"field": "v", "title": "Amount", "format": ",.2f"} // splits the chart +{"field": "v", "aggregate": "sum", "title": "Amount", ...} // correct +``` + +A stacked area grouped by month and category was silently also grouped by account, +because the tooltip named the raw value. Bands had hairline gaps where the sub-rows did +not add up, and the tooltip reported one account's share of a month — the chart was +telling the truth about its own broken grouping. + +**The rule: in a chart that aggregates, every field in every channel needs an aggregate — +including the ones that exist only to be read by a human.** The tell is a tooltip whose +number is smaller than the mark it is attached to. + +--- + +## Rows in a faceted table drift out of alignment + +Vega-Lite sizes a facet row to its **content bounds**, not to the declared height. A +panel whose marks overflow the declared 26px — an outlier dot straddling the top, a rule +drawn to the row edge — gets taller rows than a panel of plain text beside it. Four +panels, three different row pitches, and by the fourteenth row the labels sit a full row +below their numbers. + +Fix: `"bounds": "flush"` on **every** panel, not just the offending one. + +Then the second half of the same problem: flush layout does not measure the row header +either, so each header group comes to rest at its own text width — labels ragged across +sixty pixels while the DOM insists every one is `text-anchor="end"`. Fix: draw the labels +as a text mark in a panel of their own. They are a column of the table, not decoration. + +--- + +## A row's sort silently differs between panels + +An `aggregate` transform drops any field it is not told to keep. Aggregating away the +field a facet sorts on makes the row domain fall back to alphabetical — in *that panel +only*, so panels that shared a sort now name different rows in the same position. + +Fix: keep the sort field alive in the transform's `groupby`, even when nothing in that +panel plots it. + +--- + +## The chart overflows its card, or a gap appears under it + +`chartHeight` is a fixed container height. A chart sized by its data outgrows it. Measured +on a five-panel faceted table: 14 rows = 592px inside a 620px container, 15 rows = 620px +exactly, 16 rows = 649px — 29px past it, absorbed by the card's padding until it is not. + +Fix: `chartHeight: 0`, which lets the container take the rendered height. Use a fixed +height only when the spec itself declares one. + +--- + +## A line dives to the floor at the end + +The data emitted `0` for months that have not happened. Zero against a full budget reads +as maximally under budget — a lie the chart tells convincingly. + +Fix: emit `null`, which breaks the line instead. And filter the layers that would still +close over it: an area with a null `y` still closes one step past the last point, which +draws a small block into the following month that looks like data. `{"filter": +"isValid(datum.a)"}` on that layer removes it. + +--- + +## A "one-month" value looks like a ramp + +A monthly quantity drawn with the default linear interpolation ramps up through the month +before and back down the month after. A budget, a plan, a target — anything that is a +level held for a period — needs `"interpolate": "step-after"`. + +--- + +## Stacked bands show hairline gaps between them + +`"interpolate": "monotone"` smooths each band independently, so the curve fitted to a +band's top and the curve fitted to the next band's bottom — the same numbers — come out +as two different curves between data points, and the background shows through. + +Fix: `linear` on stacked areas. It is the only interpolation guaranteed to be identical +on both edges. + +--- + +## The category tint says one thing and the marks say another + +If a mark's colour or a dot's presence encodes "good/bad", compute the flag in the model, +not in the spec. `datum.actual > datum.budget` is wrong the moment a row is income, where +above budget is good news. Emitting a `u: 0|1` flag from the builder — using the same +rule the rest of the screen uses — makes the chart checkable against the table beside it, +row by row. + +--- + +## `DESCRIBE PAGE` output will not re-parse + +A page carrying a pluggable widget does not round-trip. Every string property comes back +unquoted (`spec: {"a": 1}`, `datasetName: table`) and boolean properties are omitted +entirely, so feeding a description back through the checker fails from the first widget +onward. The model is fine — the `.mxunit` contains the boolean — it is the output that is +lossy. + +Consequence: the MDL source file is the source of truth for any page with a chart on it. +Never rebuild such a page from `DESCRIBE`. + +--- + +## Playwright says the chart is the wrong size + +`browser.newPage({viewportSize})` is silently ignored in some versions — the page renders +at the default 1280×720 and the "broken" layout is the harness's, not the app's. Use +`page.setViewportSize()` explicitly, and read the SVG's own `width`/`height` attributes +rather than trusting the screenshot. + +--- + +## General + +**Measure the scenegraph; do not reason about the spec.** `scripts/check-spec.mjs` +renders headless and gives you mark positions, band edges, row pitches and the rendered +size. On this project the first hypothesis was wrong more than once — a stream graph's +gaps were blamed on interpolation, and changing it fixed nothing; measuring the band +edges found the tooltip re-graining the chart in one command. diff --git a/.claude/skills/packs/mendix-vega-charts/references/install.md b/.claude/skills/packs/mendix-vega-charts/references/install.md new file mode 100644 index 000000000..ee686a2bf --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/references/install.md @@ -0,0 +1,101 @@ +# Getting the widget into a Mendix project + +## What you need + +- Node 18+ and npm. The build uses `@mendix/pluggable-widgets-tools`. +- A Mendix project whose version matches the tools version. This widget is built with + `@mendix/pluggable-widgets-tools` 11.12.1 against a Mendix 11.13.0 app. Match the + major version to your project's. + +## 1. Install the pack + +```bash +mxcli skill add mendix-vega-charts -p MyApp.mpr +``` + +That writes the pack into `.claude/skills/mendix-vega-charts/`, widget source +included, **with the namespace already substituted for this project**. Steps 1 +and 2 of the old manual procedure are what this replaces. + +## 2. The namespace is chosen at install, not after + +A widget id looks like `acme.widget.web.vegachart.VegaChart`, and the first +segment identifies whoever built it. `skill add` derives it from the project name +and prints what it chose; `--namespace acme` overrides: + +```bash +mxcli skill add mendix-vega-charts -p MyApp.mpr --namespace acme +``` + +Three files carry it — `package.json` (`packagePath`), `src/package.xml` (the +file path) and `src/VegaChart.xml` (the id) — and they are substituted together +from one value, so they cannot drift apart. The source ships with placeholders +rather than a real namespace, so a substitution that did not happen is an error +rather than somebody else's namespace quietly shipping. + +`mxcli skill upgrade` re-substitutes what the install recorded in +`pack.lock.yaml` rather than re-deriving, because a changed widget id is not a +build error — it is every page in the app pointing at a widget that no longer +exists under that name. + +Getting it right **before** the build is the whole point. Renaming afterwards +means re-applying every page that carries the widget. + +## 3. Build + +```bash +cd .claude/skills/mendix-vega-charts/widget +npm ci +npm run build # -> dist/1.0.0/.VegaChart.mpk +``` + +The `.mpk` lands in the project's `widgets/` directly — `skill add` wrote the +build's `projectPath` relative to where the source went, so there is nothing to +copy. Verified end to end on a Mendix 11.12.1 app: every path inside the built +package is under the new namespace, and so is the id in `VegaChart.xml`. + +## 3a. Let mxcli discover it + +```bash +mxcli widget init -p MyApp.mpr +``` + +Without this, authoring a page against the widget fails with +`no definition for widget .widget.web.vegachart.VegaChart` — mxcli reads +widget definitions from `widgets/*.mpk` and has not seen the new one yet. This +is a step, not an error to debug. + +The bundle carries Vega, Vega-Lite and vega-embed, so it is large (megabytes, not +kilobytes). That is the cost of the whole grammar being available client-side. + +## 4. Commit the .mpk + +**Commit `MyApp/widgets/*.mpk`.** A `widgets/` folder in `.gitignore` looks tidy and +makes every other clone of the repository unbuildable — the project references a widget +nobody else has, and the error names a missing widget rather than a missing file. + +The widget definition cache (`MyApp/.mendix-cache/`, `deployment/`) is a different +matter and should stay ignored. + +## 5. After changing the widget's XML + +Changing a property definition invalidates every placed instance: + +``` +[error] [CE0463] "The definition of this widget has changed. Update this widget by +right-clicking it and selecting 'Update widget'..." at Vega Chart 'chartSpark' +``` + +In Studio Pro that is "Update all widgets". From MDL, re-apply the page files that carry +the widget — recreating the page writes the instance against the new definition. Pages +recreated after the rebuild are already correct; only ones written before it are flagged. + +## 6. Check it landed + +```bash +mx check MyApp.mpr # 0 errors +``` + +A widget that is present but not registered fails at build, not at run. Get the project +to 0 errors before writing any spec — otherwise a spec problem and a packaging problem +look identical from the browser. diff --git a/.claude/skills/packs/mendix-vega-charts/references/properties.md b/.claude/skills/packs/mendix-vega-charts/references/properties.md new file mode 100644 index 000000000..36d3e2bb8 --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/references/properties.md @@ -0,0 +1,82 @@ +# Widget properties, and how to write them from MDL + +## The properties + +| Key | Type | Required | What it does | +|---|---|---|---| +| `spec` | string, multiline | yes | Vega-Lite or Vega JSON. `vega-embed` chooses the language from the spec's `$schema`. | +| `chartData` | attribute (String) | no | A JSON array of row objects. | +| `datasetName` | string | no | The name the spec refers to the data by. Default `table`. Empty replaces the spec's top-level `data` instead. | +| `chartHeight` | integer | yes | Container height in px. **0 means take the height the chart renders at.** | +| `renderer` | enum `svg` / `canvas` | yes | SVG keeps marks in the DOM — selectable, styleable, and measurable by a test. Canvas is faster for very dense charts. | +| `showActions` | boolean | yes | Vega's own export/view-source menu. Off by default so the chart carries no chrome of its own. | +| `selection` | attribute (String) | no | Written with the clicked mark's datum as JSON. | +| `onClick` | action | no | Runs after `selection` is written. Leave empty and clicks are ignored entirely. | + +With a `datasetName`, the data is injected as `datasets: {: rows}` and the spec +refers to it as `{"data": {"name": "table"}}`. That is the form to use — it keeps the +spec readable and lets the same spec be compiled locally against a sample file. + +## The MDL invocation + +``` +dataview dvChart (datasource: microflow MyModule.DS_ChartData) { + pluggablewidget 'acme.widget.web.vegachart.VegaChart' chartSpend ( + chartData: ChartData, + datasetName: 'table', + chartHeight: 0, + renderer: 'svg', + showActions: false, + spec: '{ + "$schema": "https://vega.github.io/schema/vega-lite/v6.json", + "data": {"name": "table"}, + ... +}') +} +``` + +The widget needs an entity context (`needsEntityContext="true"`), so it must sit inside a +data view over the object holding the attribute. + +## Writing JSON inside an MDL string + +MDL strings are single-quoted, and the spec is one long string. Two consequences: + +**Single quotes double.** A Vega expression that quotes a literal — +`[ 'Jan','Feb' ][datum.m - 1]` — is written in MDL as: + +``` +{"calculate": "[''Jan'',''Feb''][datum.m - 1]", "as": "mon"} +``` + +and is stored unescaped. Verified by reading the model back. + +**Newlines are fine.** A multiline spec property parses and applies. But note that +`DESCRIBE PAGE` will not round-trip it (see `failure-modes.md`), so keep the MDL file the +source of truth and never rebuild a page from a description of it. + +## Click-back + +To let a click select something: + +``` +selection: SelectedPoint, +onClick: microflow MyModule.ACT_SelectPoint(Context: $currentObject) +``` + +`selection` receives the clicked datum as JSON, reduced to its own scalar fields — Vega's +internal bookkeeping (`_vgsid_`, and for aggregated marks the entire `_source_` array) is +stripped before it is written. + +**The datum is not the row you sent.** For an aggregated mark it is the aggregate, so a +clicked bar carries `{"cat":"Groceries","sum_v":11783.2}` and no id. Design the payload +so the fields you need to act on survive aggregation — carry a key in the `groupby`, or +key the follow-up query on the category name you can see. A clicked mark on a raw +(unaggregated) layer does carry the row's own fields. + +## The stylesheet + +`src/ui/VegaChart.css` gives `.vega-chart` a `width: 100%`. Without it, a spec using +`"width": "container"` measures zero and draws nothing — silently, because an empty chart +is not an error. The stylesheet only reaches the bundle because `VegaChart.tsx` imports +it. Do not remove that import as "unused". diff --git a/.claude/skills/packs/mendix-vega-charts/scripts/check-spec.mjs b/.claude/skills/packs/mendix-vega-charts/scripts/check-spec.mjs new file mode 100644 index 000000000..9ecc065e7 --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/scripts/check-spec.mjs @@ -0,0 +1,119 @@ +// Compile a Vega-Lite spec with sample rows, render it headless, and report what +// came out. No build, no browser, about a second per spec. +// +// node check-spec.mjs ../specs/line-timeseries.json # spec + its .data.json +// node check-spec.mjs ../specs/*.json # all of them +// node check-spec.mjs my-spec.json rows.json # explicit data file +// node check-spec.mjs my-spec.json rows.json --rows # dump the post-transform rows +// +// The point is not pass/fail. The scenegraph answers questions a screenshot cannot: +// how tall does this get with fifteen categories rather than thirteen, do these facet +// rows share a pitch, where did that band edge actually land. +import * as vl from "vega-lite"; +import * as vega from "vega"; +import fs from "fs"; +import path from "path"; + +const argv = process.argv.slice(2); +const dumpRows = argv.includes("--rows"); +const files = argv.filter(a => !a.startsWith("--")); + +if (files.length === 0) { + console.error("usage: node check-spec.mjs [data.json] [--rows]"); + process.exit(2); +} + +// A spec is paired with .data.json unless a second file is given. That convention +// is what lets a whole directory be checked in one command. +const pairs = []; +if (files.length === 2 && !files[1].endsWith(".data.json") && files[1].includes("data")) { + pairs.push([files[0], files[1]]); +} else { + for (const f of files.filter(f => !f.endsWith(".data.json"))) { + pairs.push([f, f.replace(/\.json$/, ".data.json")]); + } +} + +let failed = 0; + +for (const [specFile, dataFile] of pairs) { + const name = path.basename(specFile); + try { + const spec = JSON.parse(fs.readFileSync(specFile, "utf8")); + const rows = fs.existsSync(dataFile) ? JSON.parse(fs.readFileSync(dataFile, "utf8")) : null; + + // The widget injects data under `datasetName`; do the same so what is compiled + // here is what the browser gets. + const datasetName = Object.keys(spec.datasets ?? {})[0] ?? spec.data?.name ?? "table"; + if (rows) { + spec.datasets = { ...(spec.datasets ?? {}), [datasetName]: rows }; + } + + // A spec written for a Mendix card usually sizes itself to its container, which + // is zero wide here. Give it something to measure so the render is meaningful. + if (spec.width === "container") spec.width = 700; + if (spec.height === "container") spec.height = 300; + + // "Can not resolve event source: window" is what an interval selection says when + // there is no DOM to bind to. It is a fact about running headless, not about the + // spec, so it is not counted against it. + const headlessNoise = /Can not resolve event source/; + const warnings = []; + const note = (level, args) => { + const text = args.join(" "); + if (!headlessNoise.test(text)) warnings.push(`${level} ${text}`); + }; + const logger = { + level: () => logger, + error: (...a) => note("ERROR", a), + warn: (...a) => note("warn", a), + info: () => {}, + debug: () => {} + }; + + const compiled = vl.compile(spec, { logger }); + const view = new vega.View(vega.parse(compiled.spec), { renderer: "none", logger }); + await view.runAsync(); + const svg = await view.toSVG(); + + const size = svg.match(/width="(\d+)" height="(\d+)"/); + const marks = {}; + for (const m of svg.matchAll(/class="mark-(\w+)/g)) { + marks[m[1]] = (marks[m[1]] ?? 0) + 1; + } + + // Row counts per dataset say whether a transform dropped or fanned out data — + // the usual cause of a chart that is subtly wrong rather than broken. + const datasets = {}; + for (const d of compiled.spec.data ?? []) { + try { + const v = view.data(d.name); + if (Array.isArray(v) && v.length) datasets[d.name] = v.length; + } catch { + /* not a materialised dataset */ + } + } + + console.log( + `${name.padEnd(30)} ${size ? `${size[1]}x${size[2]}`.padEnd(11) : "no size "} ` + + `marks ${Object.entries(marks).map(([k, v]) => `${k}:${v}`).join(" ") || "none"}` + ); + console.log( + `${"".padEnd(30)} rows in ${rows ? rows.length : 0}` + + ` datasets ${Object.entries(datasets).map(([k, v]) => `${k}=${v}`).join(" ")}` + ); + if (warnings.length) { + failed++; + for (const w of warnings) console.log(`${"".padEnd(30)} ${w}`); + } + if (dumpRows) { + const last = Object.keys(datasets).pop(); + console.log(JSON.stringify(view.data(last).slice(0, 5), null, 1)); + } + } catch (e) { + failed++; + console.log(`${name.padEnd(30)} FAILED ${e.message.split("\n")[0]}`); + } +} + +process.exit(failed ? 1 : 0); diff --git a/.claude/skills/packs/mendix-vega-charts/scripts/package.json b/.claude/skills/packs/mendix-vega-charts/scripts/package.json new file mode 100644 index 000000000..e6c2a03da --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/scripts/package.json @@ -0,0 +1,13 @@ +{ + "name": "vega-spec-check", + "private": true, + "type": "module", + "description": "Compile and render Vega-Lite specs headless, so a chart can be checked without a Mendix build or a browser.", + "scripts": { + "check": "node check-spec.mjs ../specs/*.json" + }, + "dependencies": { + "vega": "6.3.1", + "vega-lite": "6.4.3" + } +} diff --git a/.claude/skills/packs/mendix-vega-charts/specs/README.md b/.claude/skills/packs/mendix-vega-charts/specs/README.md new file mode 100644 index 000000000..2eb337907 --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/specs/README.md @@ -0,0 +1,45 @@ +# Spec templates + +Each `.json` is a working Vega-Lite specification and `.data.json` is a +sample of the rows it expects. Every one of them compiles and renders: + +```bash +cd ../scripts && npm install && node check-spec.mjs ../specs/*.json +``` + +Start from the closest shape and change **fields**, not structure. The structure in each +is there because a simpler version of it was wrong — the comments in the `description` +fields say how. + +| Spec | Shape | Rows it expects | +|---|---|---| +| `bar-ranked.json` | Horizontal bars, sorted by value, one row per category | `{cat, v}` — repeats per category are summed | +| `line-timeseries.json` | One line over time with the last point marked | `{t, v}` — `t` is an ISO date string | +| `sparkline-cell.json` | 126×26 sparkline for a data grid cell: value, stepped reference, shaded gap, flagged periods | `{m, a, b, u, t}` — `a` null where there is no value, `u` is a 0/1 flag from the model | +| `stacked-area-by-group.json` | Stream/stacked area, banded per category, coloured per group | `{t, grp, cat, v}` | +| `scatter-brushed.json` | Every event over time, drag to select a window | `{t, v, cat, grp, merchant}` | +| `calendar-heatmap.json` | Day-of-week by week-of-year grid | `{d, v}` — `d` is an ISO date string | +| `small-multiples-table.json` | A table of aligned faceted panels: labels, sparkline per row, a numeric column | `{k, cat, ord, t, v}` for `k:"m"`, `{k, cat, ord, current}` for `k:"s"` | + +## The parts that are load-bearing + +**`{"data": {"name": "table"}}`** must match the widget's `datasetName`. + +**`"width": "container"`** needs the widget's stylesheet to be present, or it measures +zero and draws nothing. The checker substitutes 700px so a headless render is meaningful. + +**Aggregates.** Where a spec sums, every field in every channel carries an aggregate — +including tooltips. A tooltip field without one silently joins the group-by and +re-grains the chart. + +**`small-multiples-table.json`** is the fussiest and the most useful. Three things hold +it together: `"bounds": "flush"` on every panel so rows keep one pitch, the row labels +drawn as a text mark rather than as facet headers, and the sort field kept alive in the +aggregate's `groupby` so all three panels name the same row in the same position. Take +out any one and the columns drift apart while every panel insists it is correct. + +## Emitting the rows + +One JSON array of flat objects, built by a microflow over an OQL view entity. Numbers via +`formatDecimal(x, '0.00')`; `null` — never `0` — for "no value"; a discriminator column +(`k` above) when one payload feeds several panels. diff --git a/.claude/skills/packs/mendix-vega-charts/specs/bar-ranked.data.json b/.claude/skills/packs/mendix-vega-charts/specs/bar-ranked.data.json new file mode 100644 index 000000000..16cffa368 --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/specs/bar-ranked.data.json @@ -0,0 +1,3 @@ +[{"cat":"Rent","v":11813.2},{"cat":"Groceries","v":4451.0},{"cat":"Insurance","v":2560.9}, + {"cat":"Restaurants & cafes","v":1846.7},{"cat":"Utilities","v":1612.1}, + {"cat":"Transport","v":1297.4},{"cat":"Subscriptions","v":514.2}] diff --git a/.claude/skills/packs/mendix-vega-charts/specs/bar-ranked.json b/.claude/skills/packs/mendix-vega-charts/specs/bar-ranked.json new file mode 100644 index 000000000..91ab12779 --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/specs/bar-ranked.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://vega.github.io/schema/vega-lite/v6.json", + "data": {"name": "table"}, + "background": null, + "width": "container", + "height": {"step": 22}, + "config": {"view": {"stroke": null}, "axis": {"grid": false, "domain": false, "tickSize": 0}}, + "mark": {"type": "bar", "cornerRadiusEnd": 1, "color": "#1B4D4B"}, + "encoding": { + "y": {"field": "cat", "type": "nominal", "sort": "-x", "title": null}, + "x": {"field": "v", "type": "quantitative", "aggregate": "sum", "title": null, + "axis": {"format": ",.0f"}}, + "tooltip": [ + {"field": "cat", "type": "nominal", "title": "Category"}, + {"field": "v", "type": "quantitative", "aggregate": "sum", "title": "Total", "format": ",.2f"} + ] + } +} diff --git a/.claude/skills/packs/mendix-vega-charts/specs/calendar-heatmap.data.json b/.claude/skills/packs/mendix-vega-charts/specs/calendar-heatmap.data.json new file mode 100644 index 000000000..24abea886 --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/specs/calendar-heatmap.data.json @@ -0,0 +1,642 @@ +[ +{ +"d": "2026-01-02", +"v": 32 +}, +{ +"d": "2026-01-03", +"v": 49 +}, +{ +"d": "2026-01-04", +"v": 66 +}, +{ +"d": "2026-01-05", +"v": 83 +}, +{ +"d": "2026-01-07", +"v": 27 +}, +{ +"d": "2026-01-08", +"v": 44 +}, +{ +"d": "2026-01-09", +"v": 61 +}, +{ +"d": "2026-01-10", +"v": 78 +}, +{ +"d": "2026-01-12", +"v": 22 +}, +{ +"d": "2026-01-13", +"v": 39 +}, +{ +"d": "2026-01-14", +"v": 56 +}, +{ +"d": "2026-01-15", +"v": 73 +}, +{ +"d": "2026-01-17", +"v": 17 +}, +{ +"d": "2026-01-18", +"v": 34 +}, +{ +"d": "2026-01-19", +"v": 51 +}, +{ +"d": "2026-01-20", +"v": 68 +}, +{ +"d": "2026-01-22", +"v": 102 +}, +{ +"d": "2026-01-23", +"v": 29 +}, +{ +"d": "2026-01-24", +"v": 46 +}, +{ +"d": "2026-01-25", +"v": 63 +}, +{ +"d": "2026-01-27", +"v": 97 +}, +{ +"d": "2026-01-28", +"v": 24 +}, +{ +"d": "2026-01-29", +"v": 41 +}, +{ +"d": "2026-01-30", +"v": 58 +}, +{ +"d": "2026-02-01", +"v": 92 +}, +{ +"d": "2026-02-02", +"v": 19 +}, +{ +"d": "2026-02-03", +"v": 36 +}, +{ +"d": "2026-02-04", +"v": 53 +}, +{ +"d": "2026-02-06", +"v": 87 +}, +{ +"d": "2026-02-07", +"v": 104 +}, +{ +"d": "2026-02-08", +"v": 31 +}, +{ +"d": "2026-02-09", +"v": 48 +}, +{ +"d": "2026-02-11", +"v": 82 +}, +{ +"d": "2026-02-12", +"v": 99 +}, +{ +"d": "2026-02-13", +"v": 26 +}, +{ +"d": "2026-02-14", +"v": 43 +}, +{ +"d": "2026-02-16", +"v": 77 +}, +{ +"d": "2026-02-17", +"v": 94 +}, +{ +"d": "2026-02-18", +"v": 21 +}, +{ +"d": "2026-02-19", +"v": 38 +}, +{ +"d": "2026-02-21", +"v": 72 +}, +{ +"d": "2026-02-22", +"v": 89 +}, +{ +"d": "2026-02-23", +"v": 16 +}, +{ +"d": "2026-02-24", +"v": 33 +}, +{ +"d": "2026-02-26", +"v": 67 +}, +{ +"d": "2026-02-27", +"v": 84 +}, +{ +"d": "2026-02-28", +"v": 101 +}, +{ +"d": "2026-03-01", +"v": 28 +}, +{ +"d": "2026-03-03", +"v": 62 +}, +{ +"d": "2026-03-04", +"v": 79 +}, +{ +"d": "2026-03-05", +"v": 96 +}, +{ +"d": "2026-03-06", +"v": 23 +}, +{ +"d": "2026-03-08", +"v": 57 +}, +{ +"d": "2026-03-09", +"v": 74 +}, +{ +"d": "2026-03-10", +"v": 91 +}, +{ +"d": "2026-03-11", +"v": 18 +}, +{ +"d": "2026-03-13", +"v": 52 +}, +{ +"d": "2026-03-14", +"v": 69 +}, +{ +"d": "2026-03-15", +"v": 86 +}, +{ +"d": "2026-03-16", +"v": 103 +}, +{ +"d": "2026-03-18", +"v": 47 +}, +{ +"d": "2026-03-19", +"v": 64 +}, +{ +"d": "2026-03-20", +"v": 81 +}, +{ +"d": "2026-03-21", +"v": 98 +}, +{ +"d": "2026-03-23", +"v": 42 +}, +{ +"d": "2026-03-24", +"v": 59 +}, +{ +"d": "2026-03-25", +"v": 76 +}, +{ +"d": "2026-03-26", +"v": 93 +}, +{ +"d": "2026-03-28", +"v": 37 +}, +{ +"d": "2026-03-29", +"v": 54 +}, +{ +"d": "2026-03-30", +"v": 71 +}, +{ +"d": "2026-03-31", +"v": 88 +}, +{ +"d": "2026-04-02", +"v": 32 +}, +{ +"d": "2026-04-03", +"v": 49 +}, +{ +"d": "2026-04-04", +"v": 66 +}, +{ +"d": "2026-04-05", +"v": 83 +}, +{ +"d": "2026-04-07", +"v": 27 +}, +{ +"d": "2026-04-08", +"v": 44 +}, +{ +"d": "2026-04-09", +"v": 61 +}, +{ +"d": "2026-04-10", +"v": 78 +}, +{ +"d": "2026-04-12", +"v": 22 +}, +{ +"d": "2026-04-13", +"v": 39 +}, +{ +"d": "2026-04-14", +"v": 56 +}, +{ +"d": "2026-04-15", +"v": 73 +}, +{ +"d": "2026-04-17", +"v": 17 +}, +{ +"d": "2026-04-18", +"v": 34 +}, +{ +"d": "2026-04-19", +"v": 51 +}, +{ +"d": "2026-04-20", +"v": 68 +}, +{ +"d": "2026-04-22", +"v": 102 +}, +{ +"d": "2026-04-23", +"v": 29 +}, +{ +"d": "2026-04-24", +"v": 46 +}, +{ +"d": "2026-04-25", +"v": 63 +}, +{ +"d": "2026-04-27", +"v": 97 +}, +{ +"d": "2026-04-28", +"v": 24 +}, +{ +"d": "2026-04-29", +"v": 41 +}, +{ +"d": "2026-04-30", +"v": 58 +}, +{ +"d": "2026-05-02", +"v": 92 +}, +{ +"d": "2026-05-03", +"v": 19 +}, +{ +"d": "2026-05-04", +"v": 36 +}, +{ +"d": "2026-05-05", +"v": 53 +}, +{ +"d": "2026-05-07", +"v": 87 +}, +{ +"d": "2026-05-08", +"v": 104 +}, +{ +"d": "2026-05-09", +"v": 31 +}, +{ +"d": "2026-05-10", +"v": 48 +}, +{ +"d": "2026-05-12", +"v": 82 +}, +{ +"d": "2026-05-13", +"v": 99 +}, +{ +"d": "2026-05-14", +"v": 26 +}, +{ +"d": "2026-05-15", +"v": 43 +}, +{ +"d": "2026-05-17", +"v": 77 +}, +{ +"d": "2026-05-18", +"v": 94 +}, +{ +"d": "2026-05-19", +"v": 21 +}, +{ +"d": "2026-05-20", +"v": 38 +}, +{ +"d": "2026-05-22", +"v": 72 +}, +{ +"d": "2026-05-23", +"v": 89 +}, +{ +"d": "2026-05-24", +"v": 16 +}, +{ +"d": "2026-05-25", +"v": 33 +}, +{ +"d": "2026-05-27", +"v": 67 +}, +{ +"d": "2026-05-28", +"v": 84 +}, +{ +"d": "2026-05-29", +"v": 101 +}, +{ +"d": "2026-05-30", +"v": 28 +}, +{ +"d": "2026-06-01", +"v": 62 +}, +{ +"d": "2026-06-02", +"v": 79 +}, +{ +"d": "2026-06-03", +"v": 96 +}, +{ +"d": "2026-06-04", +"v": 23 +}, +{ +"d": "2026-06-06", +"v": 57 +}, +{ +"d": "2026-06-07", +"v": 74 +}, +{ +"d": "2026-06-08", +"v": 91 +}, +{ +"d": "2026-06-09", +"v": 18 +}, +{ +"d": "2026-06-11", +"v": 52 +}, +{ +"d": "2026-06-12", +"v": 69 +}, +{ +"d": "2026-06-13", +"v": 86 +}, +{ +"d": "2026-06-14", +"v": 103 +}, +{ +"d": "2026-06-16", +"v": 47 +}, +{ +"d": "2026-06-17", +"v": 64 +}, +{ +"d": "2026-06-18", +"v": 81 +}, +{ +"d": "2026-06-19", +"v": 98 +}, +{ +"d": "2026-06-21", +"v": 42 +}, +{ +"d": "2026-06-22", +"v": 59 +}, +{ +"d": "2026-06-23", +"v": 76 +}, +{ +"d": "2026-06-24", +"v": 93 +}, +{ +"d": "2026-06-26", +"v": 37 +}, +{ +"d": "2026-06-27", +"v": 54 +}, +{ +"d": "2026-06-28", +"v": 71 +}, +{ +"d": "2026-06-29", +"v": 88 +}, +{ +"d": "2026-07-01", +"v": 32 +}, +{ +"d": "2026-07-02", +"v": 49 +}, +{ +"d": "2026-07-03", +"v": 66 +}, +{ +"d": "2026-07-04", +"v": 83 +}, +{ +"d": "2026-07-06", +"v": 27 +}, +{ +"d": "2026-07-07", +"v": 44 +}, +{ +"d": "2026-07-08", +"v": 61 +}, +{ +"d": "2026-07-09", +"v": 78 +}, +{ +"d": "2026-07-11", +"v": 22 +}, +{ +"d": "2026-07-12", +"v": 39 +}, +{ +"d": "2026-07-13", +"v": 56 +}, +{ +"d": "2026-07-14", +"v": 73 +}, +{ +"d": "2026-07-16", +"v": 17 +}, +{ +"d": "2026-07-17", +"v": 34 +}, +{ +"d": "2026-07-18", +"v": 51 +}, +{ +"d": "2026-07-19", +"v": 68 +} +] \ No newline at end of file diff --git a/.claude/skills/packs/mendix-vega-charts/specs/calendar-heatmap.json b/.claude/skills/packs/mendix-vega-charts/specs/calendar-heatmap.json new file mode 100644 index 000000000..6a15ca101 --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/specs/calendar-heatmap.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://vega.github.io/schema/vega-lite/v6.json", + "data": {"name": "table"}, + "background": null, + "config": {"view": {"stroke": null}, "axis": {"domain": false, "tickSize": 0}}, + "transform": [ + {"calculate": "toDate(datum.d)", "as": "date"}, + {"calculate": "week(datum.date)", "as": "wk"}, + {"calculate": "day(datum.date)", "as": "dow"} + ], + "mark": {"type": "rect", "cornerRadius": 1, "width": 13, "height": 13}, + "encoding": { + "x": {"field": "wk", "type": "ordinal", "title": null, "axis": null}, + "y": {"field": "dow", "type": "ordinal", "title": null, + "axis": {"labelExpr": "['S','M','T','W','T','F','S'][datum.value]"}}, + "color": {"field": "v", "type": "quantitative", "aggregate": "sum", + "title": null, "scale": {"scheme": "greens"}, + "legend": {"format": ",.0f"}}, + "tooltip": [ + {"field": "date", "type": "temporal", "title": "Day", "format": "%e %b %Y"}, + {"field": "v", "type": "quantitative", "aggregate": "sum", "title": "Spend", "format": ",.2f"} + ] + } +} diff --git a/.claude/skills/packs/mendix-vega-charts/specs/line-timeseries.data.json b/.claude/skills/packs/mendix-vega-charts/specs/line-timeseries.data.json new file mode 100644 index 000000000..3c318fb50 --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/specs/line-timeseries.data.json @@ -0,0 +1,202 @@ +[ +{ +"t": "2025-01-01", +"acc": "ING", +"v": 1920 +}, +{ +"t": "2025-01-01", +"acc": "Revolut", +"v": 1760 +}, +{ +"t": "2025-02-01", +"acc": "ING", +"v": 2270 +}, +{ +"t": "2025-02-01", +"acc": "Revolut", +"v": 2110 +}, +{ +"t": "2025-03-01", +"acc": "ING", +"v": 2620 +}, +{ +"t": "2025-03-01", +"acc": "Revolut", +"v": 2460 +}, +{ +"t": "2025-04-01", +"acc": "ING", +"v": 2140 +}, +{ +"t": "2025-04-01", +"acc": "Revolut", +"v": 1980 +}, +{ +"t": "2025-05-01", +"acc": "ING", +"v": 2490 +}, +{ +"t": "2025-05-01", +"acc": "Revolut", +"v": 2330 +}, +{ +"t": "2025-06-01", +"acc": "ING", +"v": 1740 +}, +{ +"t": "2025-06-01", +"acc": "Revolut", +"v": 1580 +}, +{ +"t": "2025-07-01", +"acc": "ING", +"v": 2360 +}, +{ +"t": "2025-07-01", +"acc": "Revolut", +"v": 2200 +}, +{ +"t": "2025-08-01", +"acc": "ING", +"v": 2710 +}, +{ +"t": "2025-08-01", +"acc": "Revolut", +"v": 2550 +}, +{ +"t": "2025-09-01", +"acc": "ING", +"v": 1960 +}, +{ +"t": "2025-09-01", +"acc": "Revolut", +"v": 1800 +}, +{ +"t": "2025-10-01", +"acc": "ING", +"v": 2580 +}, +{ +"t": "2025-10-01", +"acc": "Revolut", +"v": 2420 +}, +{ +"t": "2025-11-01", +"acc": "ING", +"v": 1830 +}, +{ +"t": "2025-11-01", +"acc": "Revolut", +"v": 1670 +}, +{ +"t": "2025-12-01", +"acc": "ING", +"v": 2180 +}, +{ +"t": "2025-12-01", +"acc": "Revolut", +"v": 2020 +}, +{ +"t": "2026-01-01", +"acc": "ING", +"v": 2800 +}, +{ +"t": "2026-01-01", +"acc": "Revolut", +"v": 2640 +}, +{ +"t": "2026-02-01", +"acc": "ING", +"v": 2050 +}, +{ +"t": "2026-02-01", +"acc": "Revolut", +"v": 1890 +}, +{ +"t": "2026-03-01", +"acc": "ING", +"v": 2400 +}, +{ +"t": "2026-03-01", +"acc": "Revolut", +"v": 2240 +}, +{ +"t": "2026-04-01", +"acc": "ING", +"v": 1920 +}, +{ +"t": "2026-04-01", +"acc": "Revolut", +"v": 1760 +}, +{ +"t": "2026-05-01", +"acc": "ING", +"v": 2270 +}, +{ +"t": "2026-05-01", +"acc": "Revolut", +"v": 2110 +}, +{ +"t": "2026-06-01", +"acc": "ING", +"v": 2620 +}, +{ +"t": "2026-06-01", +"acc": "Revolut", +"v": 2460 +}, +{ +"t": "2026-07-01", +"acc": "ING", +"v": 2140 +}, +{ +"t": "2026-07-01", +"acc": "Revolut", +"v": 1980 +}, +{ +"t": "2026-08-01", +"acc": "ING", +"v": 2490 +}, +{ +"t": "2026-08-01", +"acc": "Revolut", +"v": 2330 +} +] \ No newline at end of file diff --git a/.claude/skills/packs/mendix-vega-charts/specs/line-timeseries.json b/.claude/skills/packs/mendix-vega-charts/specs/line-timeseries.json new file mode 100644 index 000000000..422d6276c --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/specs/line-timeseries.json @@ -0,0 +1,32 @@ +{ + "$schema": "https://vega.github.io/schema/vega-lite/v6.json", + "data": {"name": "table"}, + "background": null, + "width": "container", + "height": 220, + "config": {"view": {"stroke": null}}, + "layer": [ + { + "mark": {"type": "line", "color": "#1B4D4B", "strokeWidth": 1.5, "interpolate": "monotone"}, + "encoding": {"y": {"field": "v", "type": "quantitative", "aggregate": "sum", + "title": null, "axis": {"format": ",.0f", "tickCount": 4}}} + }, + { + "description": "Endpoint dot. A sparkline-style emphasis on the latest value.", + "transform": [ + {"joinaggregate": [{"op": "max", "field": "t", "as": "tMax"}]}, + {"filter": "datum.t === datum.tMax"} + ], + "mark": {"type": "point", "color": "#1B4D4B", "size": 30, "filled": true}, + "encoding": {"y": {"field": "v", "type": "quantitative", "aggregate": "sum"}} + } + ], + "encoding": { + "x": {"field": "t", "type": "temporal", "title": null, + "axis": {"format": "%b %y", "tickCount": 8, "grid": false}}, + "tooltip": [ + {"field": "t", "type": "temporal", "title": "Month", "format": "%b %Y"}, + {"field": "v", "type": "quantitative", "aggregate": "sum", "title": "Total", "format": ",.2f"} + ] + } +} diff --git a/.claude/skills/packs/mendix-vega-charts/specs/scatter-brushed.data.json b/.claude/skills/packs/mendix-vega-charts/specs/scatter-brushed.data.json new file mode 100644 index 000000000..cec262caa --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/specs/scatter-brushed.data.json @@ -0,0 +1,842 @@ +[ +{ +"t": "2026-01-01", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 0", +"v": 42.0 +}, +{ +"t": "2026-02-02", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 1", +"v": 2029.71 +}, +{ +"t": "2026-03-03", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 2", +"v": 261.0 +}, +{ +"t": "2026-04-04", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 3", +"v": 50.14 +}, +{ +"t": "2026-05-05", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 4", +"v": 69.0 +}, +{ +"t": "2026-06-06", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 0", +"v": 1268.57 +}, +{ +"t": "2026-07-07", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 1", +"v": 153.0 +}, +{ +"t": "2026-08-08", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 2", +"v": 27.0 +}, +{ +"t": "2026-01-09", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 3", +"v": 96.0 +}, +{ +"t": "2026-02-10", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 4", +"v": 1839.43 +}, +{ +"t": "2026-03-11", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 0", +"v": 234.0 +}, +{ +"t": "2026-04-12", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 1", +"v": 44.36 +}, +{ +"t": "2026-05-13", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 2", +"v": 60.0 +}, +{ +"t": "2026-06-14", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 3", +"v": 1078.29 +}, +{ +"t": "2026-07-15", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 4", +"v": 126.0 +}, +{ +"t": "2026-08-16", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 0", +"v": 61.71 +}, +{ +"t": "2026-01-17", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 1", +"v": 87.0 +}, +{ +"t": "2026-02-18", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 2", +"v": 1649.14 +}, +{ +"t": "2026-03-19", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 3", +"v": 207.0 +}, +{ +"t": "2026-04-20", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 4", +"v": 38.57 +}, +{ +"t": "2026-05-21", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 0", +"v": 51.0 +}, +{ +"t": "2026-06-22", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 1", +"v": 888.0 +}, +{ +"t": "2026-07-23", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 2", +"v": 288.0 +}, +{ +"t": "2026-08-24", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 3", +"v": 55.93 +}, +{ +"t": "2026-01-25", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 4", +"v": 78.0 +}, +{ +"t": "2026-02-26", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 0", +"v": 1458.86 +}, +{ +"t": "2026-03-27", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 1", +"v": 180.0 +}, +{ +"t": "2026-04-01", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 2", +"v": 32.79 +}, +{ +"t": "2026-05-02", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 3", +"v": 42.0 +}, +{ +"t": "2026-06-03", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 4", +"v": 2029.71 +}, +{ +"t": "2026-07-04", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 0", +"v": 261.0 +}, +{ +"t": "2026-08-05", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 1", +"v": 50.14 +}, +{ +"t": "2026-01-06", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 2", +"v": 69.0 +}, +{ +"t": "2026-02-07", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 3", +"v": 1268.57 +}, +{ +"t": "2026-03-08", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 4", +"v": 153.0 +}, +{ +"t": "2026-04-09", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 0", +"v": 27.0 +}, +{ +"t": "2026-05-10", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 1", +"v": 96.0 +}, +{ +"t": "2026-06-11", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 2", +"v": 1839.43 +}, +{ +"t": "2026-07-12", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 3", +"v": 234.0 +}, +{ +"t": "2026-08-13", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 4", +"v": 44.36 +}, +{ +"t": "2026-01-14", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 0", +"v": 60.0 +}, +{ +"t": "2026-02-15", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 1", +"v": 1078.29 +}, +{ +"t": "2026-03-16", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 2", +"v": 126.0 +}, +{ +"t": "2026-04-17", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 3", +"v": 61.71 +}, +{ +"t": "2026-05-18", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 4", +"v": 87.0 +}, +{ +"t": "2026-06-19", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 0", +"v": 1649.14 +}, +{ +"t": "2026-07-20", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 1", +"v": 207.0 +}, +{ +"t": "2026-08-21", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 2", +"v": 38.57 +}, +{ +"t": "2026-01-22", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 3", +"v": 51.0 +}, +{ +"t": "2026-02-23", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 4", +"v": 888.0 +}, +{ +"t": "2026-03-24", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 0", +"v": 288.0 +}, +{ +"t": "2026-04-25", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 1", +"v": 55.93 +}, +{ +"t": "2026-05-26", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 2", +"v": 78.0 +}, +{ +"t": "2026-06-27", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 3", +"v": 1458.86 +}, +{ +"t": "2026-07-01", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 4", +"v": 180.0 +}, +{ +"t": "2026-08-02", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 0", +"v": 32.79 +}, +{ +"t": "2026-01-03", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 1", +"v": 42.0 +}, +{ +"t": "2026-02-04", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 2", +"v": 2029.71 +}, +{ +"t": "2026-03-05", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 3", +"v": 261.0 +}, +{ +"t": "2026-04-06", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 4", +"v": 50.14 +}, +{ +"t": "2026-05-07", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 0", +"v": 69.0 +}, +{ +"t": "2026-06-08", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 1", +"v": 1268.57 +}, +{ +"t": "2026-07-09", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 2", +"v": 153.0 +}, +{ +"t": "2026-08-10", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 3", +"v": 27.0 +}, +{ +"t": "2026-01-11", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 4", +"v": 96.0 +}, +{ +"t": "2026-02-12", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 0", +"v": 1839.43 +}, +{ +"t": "2026-03-13", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 1", +"v": 234.0 +}, +{ +"t": "2026-04-14", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 2", +"v": 44.36 +}, +{ +"t": "2026-05-15", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 3", +"v": 60.0 +}, +{ +"t": "2026-06-16", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 4", +"v": 1078.29 +}, +{ +"t": "2026-07-17", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 0", +"v": 126.0 +}, +{ +"t": "2026-08-18", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 1", +"v": 61.71 +}, +{ +"t": "2026-01-19", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 2", +"v": 87.0 +}, +{ +"t": "2026-02-20", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 3", +"v": 1649.14 +}, +{ +"t": "2026-03-21", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 4", +"v": 207.0 +}, +{ +"t": "2026-04-22", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 0", +"v": 38.57 +}, +{ +"t": "2026-05-23", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 1", +"v": 51.0 +}, +{ +"t": "2026-06-24", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 2", +"v": 888.0 +}, +{ +"t": "2026-07-25", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 3", +"v": 288.0 +}, +{ +"t": "2026-08-26", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 4", +"v": 55.93 +}, +{ +"t": "2026-01-27", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 0", +"v": 78.0 +}, +{ +"t": "2026-02-01", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 1", +"v": 1458.86 +}, +{ +"t": "2026-03-02", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 2", +"v": 180.0 +}, +{ +"t": "2026-04-03", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 3", +"v": 32.79 +}, +{ +"t": "2026-05-04", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 4", +"v": 42.0 +}, +{ +"t": "2026-06-05", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 0", +"v": 2029.71 +}, +{ +"t": "2026-07-06", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 1", +"v": 261.0 +}, +{ +"t": "2026-08-07", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 2", +"v": 50.14 +}, +{ +"t": "2026-01-08", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 3", +"v": 69.0 +}, +{ +"t": "2026-02-09", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 4", +"v": 1268.57 +}, +{ +"t": "2026-03-10", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 0", +"v": 153.0 +}, +{ +"t": "2026-04-11", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 1", +"v": 27.0 +}, +{ +"t": "2026-05-12", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 2", +"v": 96.0 +}, +{ +"t": "2026-06-13", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 3", +"v": 1839.43 +}, +{ +"t": "2026-07-14", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 4", +"v": 234.0 +}, +{ +"t": "2026-08-15", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 0", +"v": 44.36 +}, +{ +"t": "2026-01-16", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 1", +"v": 60.0 +}, +{ +"t": "2026-02-17", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 2", +"v": 1078.29 +}, +{ +"t": "2026-03-18", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 3", +"v": 126.0 +}, +{ +"t": "2026-04-19", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 4", +"v": 61.71 +}, +{ +"t": "2026-05-20", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 0", +"v": 87.0 +}, +{ +"t": "2026-06-21", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 1", +"v": 1649.14 +}, +{ +"t": "2026-07-22", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 2", +"v": 207.0 +}, +{ +"t": "2026-08-23", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 3", +"v": 38.57 +}, +{ +"t": "2026-01-24", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 4", +"v": 51.0 +}, +{ +"t": "2026-02-25", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 0", +"v": 888.0 +}, +{ +"t": "2026-03-26", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 1", +"v": 288.0 +}, +{ +"t": "2026-04-27", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 2", +"v": 55.93 +}, +{ +"t": "2026-05-01", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 3", +"v": 78.0 +}, +{ +"t": "2026-06-02", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 4", +"v": 1458.86 +}, +{ +"t": "2026-07-03", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 0", +"v": 180.0 +}, +{ +"t": "2026-08-04", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 1", +"v": 32.79 +}, +{ +"t": "2026-01-05", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 2", +"v": 42.0 +}, +{ +"t": "2026-02-06", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 3", +"v": 2029.71 +}, +{ +"t": "2026-03-07", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 4", +"v": 261.0 +}, +{ +"t": "2026-04-08", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 0", +"v": 50.14 +}, +{ +"t": "2026-05-09", +"cat": "Groceries", +"grp": "Daily living", +"merchant": "Groceries shop 1", +"v": 69.0 +}, +{ +"t": "2026-06-10", +"cat": "Rent", +"grp": "Housing", +"merchant": "Rent shop 2", +"v": 1268.57 +}, +{ +"t": "2026-07-11", +"cat": "Shopping", +"grp": "Lifestyle", +"merchant": "Shopping shop 3", +"v": 153.0 +}, +{ +"t": "2026-08-12", +"cat": "Transport", +"grp": "Daily living", +"merchant": "Transport shop 4", +"v": 27.0 +} +] \ No newline at end of file diff --git a/.claude/skills/packs/mendix-vega-charts/specs/scatter-brushed.json b/.claude/skills/packs/mendix-vega-charts/specs/scatter-brushed.json new file mode 100644 index 000000000..6c3613dcb --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/specs/scatter-brushed.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://vega.github.io/schema/vega-lite/v6.json", + "data": {"name": "table"}, + "background": null, + "width": "container", + "height": 320, + "config": {"view": {"stroke": null}}, + "params": [ + {"name": "brush", "select": {"type": "interval", "encodings": ["x"]}} + ], + "mark": {"type": "circle", "size": 26, "opacity": 0.55}, + "encoding": { + "x": {"field": "t", "type": "temporal", "title": null, "axis": {"format": "%b %y", "grid": false}}, + "y": {"field": "v", "type": "quantitative", "title": null, + "scale": {"type": "sqrt"}, "axis": {"format": ",.0f"}}, + "color": { + "condition": {"param": "brush", "field": "grp", "type": "nominal", "title": null}, + "value": "#D9D4C7" + }, + "tooltip": [ + {"field": "merchant", "type": "nominal", "title": "Merchant"}, + {"field": "cat", "type": "nominal", "title": "Category"}, + {"field": "t", "type": "temporal", "title": "Date", "format": "%e %b %Y"}, + {"field": "v", "type": "quantitative", "title": "Amount", "format": ",.2f"} + ] + } +} diff --git a/.claude/skills/packs/mendix-vega-charts/specs/small-multiples-table.data.json b/.claude/skills/packs/mendix-vega-charts/specs/small-multiples-table.data.json new file mode 100644 index 000000000..567ea9d34 --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/specs/small-multiples-table.data.json @@ -0,0 +1,1958 @@ +[ +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-01-01", +"acc": "ING", +"v": 1091.53 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-01-01", +"acc": "Revolut", +"v": 1091.53 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-02-01", +"acc": "ING", +"v": 1321.15 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-02-01", +"acc": "Revolut", +"v": 1321.15 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-03-01", +"acc": "ING", +"v": 1339.66 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-03-01", +"acc": "Revolut", +"v": 1339.66 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-04-01", +"acc": "ING", +"v": 1130.03 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-04-01", +"acc": "Revolut", +"v": 1130.03 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-05-01", +"acc": "ING", +"v": 885.01 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-05-01", +"acc": "Revolut", +"v": 885.01 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-06-01", +"acc": "ING", +"v": 829.85 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-06-01", +"acc": "Revolut", +"v": 829.85 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-07-01", +"acc": "ING", +"v": 1015.28 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-07-01", +"acc": "Revolut", +"v": 1015.28 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-08-01", +"acc": "ING", +"v": 1270.8 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-08-01", +"acc": "Revolut", +"v": 1270.8 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-09-01", +"acc": "ING", +"v": 1361.5 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-09-01", +"acc": "Revolut", +"v": 1361.5 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-10-01", +"acc": "ING", +"v": 1203.98 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-10-01", +"acc": "Revolut", +"v": 1203.98 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-11-01", +"acc": "ING", +"v": 943.07 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-11-01", +"acc": "Revolut", +"v": 943.07 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-12-01", +"acc": "ING", +"v": 818.65 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2025-12-01", +"acc": "Revolut", +"v": 818.65 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2026-01-01", +"acc": "ING", +"v": 945.1 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2026-01-01", +"acc": "Revolut", +"v": 945.1 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2026-02-01", +"acc": "ING", +"v": 1206.18 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2026-02-01", +"acc": "Revolut", +"v": 1206.18 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2026-03-01", +"acc": "ING", +"v": 1361.84 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2026-03-01", +"acc": "Revolut", +"v": 1361.84 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2026-04-01", +"acc": "ING", +"v": 1268.98 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2026-04-01", +"acc": "Revolut", +"v": 1268.98 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2026-05-01", +"acc": "ING", +"v": 1012.96 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2026-05-01", +"acc": "Revolut", +"v": 1012.96 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2026-06-01", +"acc": "ING", +"v": 829.18 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2026-06-01", +"acc": "Revolut", +"v": 829.18 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2026-07-01", +"acc": "ING", +"v": 886.59 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2026-07-01", +"acc": "Revolut", +"v": 886.59 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2026-08-01", +"acc": "ING", +"v": 1132.42 +}, +{ +"k": "m", +"cat": "Salary", +"ord": 43661, +"t": "2026-08-01", +"acc": "Revolut", +"v": 1132.42 +}, +{ +"k": "s", +"cat": "Salary", +"ord": 43661, +"current": 43661 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-01-01", +"acc": "ING", +"v": 357.45 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-01-01", +"acc": "Revolut", +"v": 357.45 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-02-01", +"acc": "ING", +"v": 362.46 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-02-01", +"acc": "Revolut", +"v": 362.46 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-03-01", +"acc": "ING", +"v": 305.74 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-03-01", +"acc": "Revolut", +"v": 305.74 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-04-01", +"acc": "ING", +"v": 239.45 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-04-01", +"acc": "Revolut", +"v": 239.45 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-05-01", +"acc": "ING", +"v": 224.53 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-05-01", +"acc": "Revolut", +"v": 224.53 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-06-01", +"acc": "ING", +"v": 274.7 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-06-01", +"acc": "Revolut", +"v": 274.7 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-07-01", +"acc": "ING", +"v": 343.83 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-07-01", +"acc": "Revolut", +"v": 343.83 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-08-01", +"acc": "ING", +"v": 368.37 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-08-01", +"acc": "Revolut", +"v": 368.37 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-09-01", +"acc": "ING", +"v": 325.75 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-09-01", +"acc": "Revolut", +"v": 325.75 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-10-01", +"acc": "ING", +"v": 255.16 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-10-01", +"acc": "Revolut", +"v": 255.16 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-11-01", +"acc": "ING", +"v": 221.49 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-11-01", +"acc": "Revolut", +"v": 221.49 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-12-01", +"acc": "ING", +"v": 255.71 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2025-12-01", +"acc": "Revolut", +"v": 255.71 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2026-01-01", +"acc": "ING", +"v": 326.35 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2026-01-01", +"acc": "Revolut", +"v": 326.35 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2026-02-01", +"acc": "ING", +"v": 368.46 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2026-02-01", +"acc": "Revolut", +"v": 368.46 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2026-03-01", +"acc": "ING", +"v": 343.34 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2026-03-01", +"acc": "Revolut", +"v": 343.34 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2026-04-01", +"acc": "ING", +"v": 274.07 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2026-04-01", +"acc": "Revolut", +"v": 274.07 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2026-05-01", +"acc": "ING", +"v": 224.34 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2026-05-01", +"acc": "Revolut", +"v": 224.34 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2026-06-01", +"acc": "ING", +"v": 239.88 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2026-06-01", +"acc": "Revolut", +"v": 239.88 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2026-07-01", +"acc": "ING", +"v": 306.39 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2026-07-01", +"acc": "Revolut", +"v": 306.39 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2026-08-01", +"acc": "ING", +"v": 362.73 +}, +{ +"k": "m", +"cat": "Rent", +"ord": 11813, +"t": "2026-08-01", +"acc": "Revolut", +"v": 362.73 +}, +{ +"k": "s", +"cat": "Rent", +"ord": 11813, +"current": 11813 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-01-01", +"acc": "ING", +"v": 136.57 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-01-01", +"acc": "Revolut", +"v": 136.57 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-02-01", +"acc": "ING", +"v": 115.2 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-02-01", +"acc": "Revolut", +"v": 115.2 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-03-01", +"acc": "ING", +"v": 90.22 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-03-01", +"acc": "Revolut", +"v": 90.22 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-04-01", +"acc": "ING", +"v": 84.6 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-04-01", +"acc": "Revolut", +"v": 84.6 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-05-01", +"acc": "ING", +"v": 103.5 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-05-01", +"acc": "Revolut", +"v": 103.5 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-06-01", +"acc": "ING", +"v": 129.55 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-06-01", +"acc": "Revolut", +"v": 129.55 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-07-01", +"acc": "ING", +"v": 138.8 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-07-01", +"acc": "Revolut", +"v": 138.8 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-08-01", +"acc": "ING", +"v": 122.74 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-08-01", +"acc": "Revolut", +"v": 122.74 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-09-01", +"acc": "ING", +"v": 96.14 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-09-01", +"acc": "Revolut", +"v": 96.14 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-10-01", +"acc": "ING", +"v": 83.46 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-10-01", +"acc": "Revolut", +"v": 83.46 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-11-01", +"acc": "ING", +"v": 96.35 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-11-01", +"acc": "Revolut", +"v": 96.35 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-12-01", +"acc": "ING", +"v": 122.96 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2025-12-01", +"acc": "Revolut", +"v": 122.96 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2026-01-01", +"acc": "ING", +"v": 138.83 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2026-01-01", +"acc": "Revolut", +"v": 138.83 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2026-02-01", +"acc": "ING", +"v": 129.37 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2026-02-01", +"acc": "Revolut", +"v": 129.37 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2026-03-01", +"acc": "ING", +"v": 103.27 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2026-03-01", +"acc": "Revolut", +"v": 103.27 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2026-04-01", +"acc": "ING", +"v": 84.53 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2026-04-01", +"acc": "Revolut", +"v": 84.53 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2026-05-01", +"acc": "ING", +"v": 90.38 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2026-05-01", +"acc": "Revolut", +"v": 90.38 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2026-06-01", +"acc": "ING", +"v": 115.44 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2026-06-01", +"acc": "Revolut", +"v": 115.44 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2026-07-01", +"acc": "ING", +"v": 136.67 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2026-07-01", +"acc": "Revolut", +"v": 136.67 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2026-08-01", +"acc": "ING", +"v": 134.55 +}, +{ +"k": "m", +"cat": "Groceries", +"ord": 4451, +"t": "2026-08-01", +"acc": "Revolut", +"v": 134.55 +}, +{ +"k": "s", +"cat": "Groceries", +"ord": 4451, +"current": 4451 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-01-01", +"acc": "ING", +"v": 66.26 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-01-01", +"acc": "Revolut", +"v": 66.26 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-02-01", +"acc": "ING", +"v": 51.89 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-02-01", +"acc": "Revolut", +"v": 51.89 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-03-01", +"acc": "ING", +"v": 48.66 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-03-01", +"acc": "Revolut", +"v": 48.66 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-04-01", +"acc": "ING", +"v": 59.53 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-04-01", +"acc": "Revolut", +"v": 59.53 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-05-01", +"acc": "ING", +"v": 74.51 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-05-01", +"acc": "Revolut", +"v": 74.51 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-06-01", +"acc": "ING", +"v": 79.83 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-06-01", +"acc": "Revolut", +"v": 79.83 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-07-01", +"acc": "ING", +"v": 70.59 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-07-01", +"acc": "Revolut", +"v": 70.59 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-08-01", +"acc": "ING", +"v": 55.3 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-08-01", +"acc": "Revolut", +"v": 55.3 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-09-01", +"acc": "ING", +"v": 48.0 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-09-01", +"acc": "Revolut", +"v": 48.0 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-10-01", +"acc": "ING", +"v": 55.41 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-10-01", +"acc": "Revolut", +"v": 55.41 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-11-01", +"acc": "ING", +"v": 70.72 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-11-01", +"acc": "Revolut", +"v": 70.72 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-12-01", +"acc": "ING", +"v": 79.85 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2025-12-01", +"acc": "Revolut", +"v": 79.85 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2026-01-01", +"acc": "ING", +"v": 74.4 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2026-01-01", +"acc": "Revolut", +"v": 74.4 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2026-02-01", +"acc": "ING", +"v": 59.39 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2026-02-01", +"acc": "Revolut", +"v": 59.39 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2026-03-01", +"acc": "ING", +"v": 48.62 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2026-03-01", +"acc": "Revolut", +"v": 48.62 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2026-04-01", +"acc": "ING", +"v": 51.98 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2026-04-01", +"acc": "Revolut", +"v": 51.98 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2026-05-01", +"acc": "ING", +"v": 66.4 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2026-05-01", +"acc": "Revolut", +"v": 66.4 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2026-06-01", +"acc": "ING", +"v": 78.61 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2026-06-01", +"acc": "Revolut", +"v": 78.61 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2026-07-01", +"acc": "ING", +"v": 77.39 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2026-07-01", +"acc": "Revolut", +"v": 77.39 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2026-08-01", +"acc": "ING", +"v": 63.86 +}, +{ +"k": "m", +"cat": "Insurance", +"ord": 2560, +"t": "2026-08-01", +"acc": "Revolut", +"v": 63.86 +}, +{ +"k": "s", +"cat": "Insurance", +"ord": 2560, +"current": 2560 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-01-01", +"acc": "ING", +"v": 26.29 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-01-01", +"acc": "Revolut", +"v": 26.29 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-02-01", +"acc": "ING", +"v": 24.65 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-02-01", +"acc": "Revolut", +"v": 24.65 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-03-01", +"acc": "ING", +"v": 30.16 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-03-01", +"acc": "Revolut", +"v": 30.16 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-04-01", +"acc": "ING", +"v": 37.75 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-04-01", +"acc": "Revolut", +"v": 37.75 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-05-01", +"acc": "ING", +"v": 40.44 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-05-01", +"acc": "Revolut", +"v": 40.44 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-06-01", +"acc": "ING", +"v": 35.77 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-06-01", +"acc": "Revolut", +"v": 35.77 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-07-01", +"acc": "ING", +"v": 28.02 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-07-01", +"acc": "Revolut", +"v": 28.02 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-08-01", +"acc": "ING", +"v": 24.32 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-08-01", +"acc": "Revolut", +"v": 24.32 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-09-01", +"acc": "ING", +"v": 28.08 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-09-01", +"acc": "Revolut", +"v": 28.08 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-10-01", +"acc": "ING", +"v": 35.83 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-10-01", +"acc": "Revolut", +"v": 35.83 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-11-01", +"acc": "ING", +"v": 40.46 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-11-01", +"acc": "Revolut", +"v": 40.46 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-12-01", +"acc": "ING", +"v": 37.7 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2025-12-01", +"acc": "Revolut", +"v": 37.7 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2026-01-01", +"acc": "ING", +"v": 30.09 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2026-01-01", +"acc": "Revolut", +"v": 30.09 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2026-02-01", +"acc": "ING", +"v": 24.63 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2026-02-01", +"acc": "Revolut", +"v": 24.63 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2026-03-01", +"acc": "ING", +"v": 26.34 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2026-03-01", +"acc": "Revolut", +"v": 26.34 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2026-04-01", +"acc": "ING", +"v": 33.64 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2026-04-01", +"acc": "Revolut", +"v": 33.64 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2026-05-01", +"acc": "ING", +"v": 39.83 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2026-05-01", +"acc": "Revolut", +"v": 39.83 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2026-06-01", +"acc": "ING", +"v": 39.21 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2026-06-01", +"acc": "Revolut", +"v": 39.21 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2026-07-01", +"acc": "ING", +"v": 32.35 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2026-07-01", +"acc": "Revolut", +"v": 32.35 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2026-08-01", +"acc": "ING", +"v": 25.57 +}, +{ +"k": "m", +"cat": "Transport", +"ord": 1297, +"t": "2026-08-01", +"acc": "Revolut", +"v": 25.57 +}, +{ +"k": "s", +"cat": "Transport", +"ord": 1297, +"current": 1297 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-01-01", +"acc": "ING", +"v": 9.77 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-01-01", +"acc": "Revolut", +"v": 9.77 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-02-01", +"acc": "ING", +"v": 11.95 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-02-01", +"acc": "Revolut", +"v": 11.95 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-03-01", +"acc": "ING", +"v": 14.96 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-03-01", +"acc": "Revolut", +"v": 14.96 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-04-01", +"acc": "ING", +"v": 16.03 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-04-01", +"acc": "Revolut", +"v": 16.03 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-05-01", +"acc": "ING", +"v": 14.17 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-05-01", +"acc": "Revolut", +"v": 14.17 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-06-01", +"acc": "ING", +"v": 11.1 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-06-01", +"acc": "Revolut", +"v": 11.1 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-07-01", +"acc": "ING", +"v": 9.64 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-07-01", +"acc": "Revolut", +"v": 9.64 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-08-01", +"acc": "ING", +"v": 11.13 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-08-01", +"acc": "Revolut", +"v": 11.13 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-09-01", +"acc": "ING", +"v": 14.2 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-09-01", +"acc": "Revolut", +"v": 14.2 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-10-01", +"acc": "ING", +"v": 16.03 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-10-01", +"acc": "Revolut", +"v": 16.03 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-11-01", +"acc": "ING", +"v": 14.94 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-11-01", +"acc": "Revolut", +"v": 14.94 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-12-01", +"acc": "ING", +"v": 11.93 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2025-12-01", +"acc": "Revolut", +"v": 11.93 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2026-01-01", +"acc": "ING", +"v": 9.76 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2026-01-01", +"acc": "Revolut", +"v": 9.76 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2026-02-01", +"acc": "ING", +"v": 10.44 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2026-02-01", +"acc": "Revolut", +"v": 10.44 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2026-03-01", +"acc": "ING", +"v": 13.33 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2026-03-01", +"acc": "Revolut", +"v": 13.33 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2026-04-01", +"acc": "ING", +"v": 15.78 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2026-04-01", +"acc": "Revolut", +"v": 15.78 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2026-05-01", +"acc": "ING", +"v": 15.54 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2026-05-01", +"acc": "Revolut", +"v": 15.54 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2026-06-01", +"acc": "ING", +"v": 12.82 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2026-06-01", +"acc": "Revolut", +"v": 12.82 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2026-07-01", +"acc": "ING", +"v": 10.13 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2026-07-01", +"acc": "Revolut", +"v": 10.13 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2026-08-01", +"acc": "ING", +"v": 9.94 +}, +{ +"k": "m", +"cat": "Subscriptions", +"ord": 514, +"t": "2026-08-01", +"acc": "Revolut", +"v": 9.94 +}, +{ +"k": "s", +"cat": "Subscriptions", +"ord": 514, +"current": 514 +} +] \ No newline at end of file diff --git a/.claude/skills/packs/mendix-vega-charts/specs/small-multiples-table.json b/.claude/skills/packs/mendix-vega-charts/specs/small-multiples-table.json new file mode 100644 index 000000000..dc7deb77d --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/specs/small-multiples-table.json @@ -0,0 +1,66 @@ +{ + "$schema": "https://vega.github.io/schema/vega-lite/v6.json", + "data": {"name": "table"}, + "background": null, + "config": {"view": {"stroke": null}, "concat": {"spacing": 14}, "facet": {"spacing": 3}}, + "hconcat": [ + { + "description": "Row labels as a text mark in a panel of their own. Flush bounds does not measure a facet header, so a real header comes to rest at its own text width and the labels go ragged.", + "transform": [{"filter": "datum.k === 's'"}], + "bounds": "flush", + "facet": {"row": {"field": "cat", "type": "nominal", "title": null, + "sort": {"op": "max", "field": "ord", "order": "descending"}, + "header": {"labels": false}}}, + "spec": { + "width": 118, "height": 26, + "mark": {"type": "text", "align": "right", "fontSize": 10, "color": "#17181A"}, + "encoding": { + "x": {"datum": 118, "axis": null, "scale": {"domain": [0, 118]}}, + "text": {"field": "cat", "type": "nominal"} + } + } + }, + { + "description": "One sparkline per row. Flush bounds on EVERY panel — a panel whose marks overflow its declared height gets taller rows than a panel of plain text beside it, and the columns drift apart.", + "transform": [{"filter": "datum.k === 'm'"}], + "bounds": "flush", + "facet": {"row": {"field": "cat", "type": "nominal", "title": null, + "sort": {"op": "max", "field": "ord", "order": "descending"}, + "header": {"labels": false}}}, + "resolve": {"scale": {"y": "independent"}}, + "spec": { + "width": 300, "height": 26, + "transform": [ + {"description": "Aggregate first. The model emits a row per period per account; a line mark does not aggregate, it joins them in order and draws a sawtooth. ord stays in the groupby only to keep the row sort alive.", + "aggregate": [{"op": "sum", "field": "v", "as": "v"}], "groupby": ["cat", "t", "ord"]} + ], + "mark": {"type": "line", "color": "#1B4D4B", "strokeWidth": 1}, + "encoding": { + "x": {"field": "t", "type": "temporal", "axis": null}, + "y": {"field": "v", "type": "quantitative", "axis": null}, + "tooltip": [ + {"field": "cat", "type": "nominal", "title": "Category"}, + {"field": "t", "type": "temporal", "title": "Month", "format": "%b %Y"}, + {"field": "v", "type": "quantitative", "aggregate": "sum", "title": "Amount", "format": ",.2f"} + ] + } + } + }, + { + "description": "A numeric column, right-aligned, same row height and same sort so it lines up with the panels beside it.", + "transform": [{"filter": "datum.k === 's'"}], + "bounds": "flush", + "facet": {"row": {"field": "cat", "type": "nominal", "title": null, + "sort": {"op": "max", "field": "ord", "order": "descending"}, + "header": {"labels": false}}}, + "spec": { + "width": 74, "height": 26, + "mark": {"type": "text", "align": "right", "fontSize": 10, "color": "#17181A"}, + "encoding": { + "x": {"datum": 74, "axis": null, "scale": {"domain": [0, 74]}}, + "text": {"field": "current", "type": "quantitative", "format": ",.0f"} + } + } + } + ] +} diff --git a/.claude/skills/packs/mendix-vega-charts/specs/sparkline-cell.data.json b/.claude/skills/packs/mendix-vega-charts/specs/sparkline-cell.data.json new file mode 100644 index 000000000..a7d04be8d --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/specs/sparkline-cell.data.json @@ -0,0 +1,86 @@ +[ +{ +"m": 1, +"a": 671, +"b": 650, +"u": 1, +"t": "\u20ac 671.00 of \u20ac 650.00" +}, +{ +"m": 2, +"a": 653, +"b": 650, +"u": 0, +"t": "\u20ac 653.00 of \u20ac 650.00" +}, +{ +"m": 3, +"a": 655, +"b": 650, +"u": 0, +"t": "\u20ac 655.00 of \u20ac 650.00" +}, +{ +"m": 4, +"a": 634, +"b": 650, +"u": 0, +"t": "\u20ac 634.00 of \u20ac 650.00" +}, +{ +"m": 5, +"a": 544, +"b": 650, +"u": 0, +"t": "\u20ac 544.00 of \u20ac 650.00" +}, +{ +"m": 6, +"a": 698, +"b": 650, +"u": 1, +"t": "\u20ac 698.00 of \u20ac 650.00" +}, +{ +"m": 7, +"a": 630, +"b": 650, +"u": 0, +"t": "\u20ac 630.00 of \u20ac 650.00" +}, +{ +"m": 8, +"a": 566, +"b": 650, +"u": 0, +"t": "\u20ac 566.00 of \u20ac 650.00" +}, +{ +"m": 9, +"a": null, +"b": 650, +"u": 0, +"t": "" +}, +{ +"m": 10, +"a": null, +"b": 650, +"u": 0, +"t": "" +}, +{ +"m": 11, +"a": null, +"b": 650, +"u": 0, +"t": "" +}, +{ +"m": 12, +"a": null, +"b": 650, +"u": 0, +"t": "" +} +] \ No newline at end of file diff --git a/.claude/skills/packs/mendix-vega-charts/specs/sparkline-cell.json b/.claude/skills/packs/mendix-vega-charts/specs/sparkline-cell.json new file mode 100644 index 000000000..cdac9f706 --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/specs/sparkline-cell.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://vega.github.io/schema/vega-lite/v6.json", + "data": {"name": "table"}, + "background": null, + "width": 126, + "height": 26, + "padding": 2, + "autosize": {"type": "fit", "contains": "padding"}, + "config": {"view": {"stroke": null}}, + "layer": [ + { + "description": "The gap between the value and its reference, shaded. Filtered to periods that have a value: an area whose y is null still closes one step past the last point, which draws a block into the following period that looks like data.", + "transform": [{"filter": "isValid(datum.a)"}], + "mark": {"type": "area", "color": "#1B4D4B", "opacity": 0.08}, + "encoding": {"y": {"field": "a", "type": "quantitative"}, "y2": {"field": "b"}} + }, + { + "description": "The reference, as a step. A level held for a period is not a trajectory between periods, and a one-period override drawn linearly looks like a ramp.", + "mark": {"type": "line", "color": "#B9B1A1", "strokeWidth": 1, "interpolate": "step-after"}, + "encoding": {"y": {"field": "b", "type": "quantitative"}} + }, + { + "description": "The value. Nulls break the line rather than drawing it to zero.", + "mark": {"type": "line", "color": "#1B4D4B", "strokeWidth": 1.2}, + "encoding": {"y": {"field": "a", "type": "quantitative"}} + }, + { + "description": "Flagged periods, the only strong colour on the row. The flag is computed by the model, never as a > b in the spec — above the reference is good news on an income row.", + "transform": [{"filter": "isValid(datum.a) && datum.u === 1"}], + "mark": {"type": "point", "color": "#A8321E", "size": 12, "filled": true}, + "encoding": {"y": {"field": "a", "type": "quantitative"}} + } + ], + "encoding": { + "x": {"field": "m", "type": "quantitative", "scale": {"domain": [1, 12], "nice": false}, "axis": null}, + "y": {"axis": null, "scale": {"zero": false, "nice": false}}, + "tooltip": [{"field": "t", "type": "nominal", "title": "Cell"}] + } +} diff --git a/.claude/skills/packs/mendix-vega-charts/specs/stacked-area-by-group.data.json b/.claude/skills/packs/mendix-vega-charts/specs/stacked-area-by-group.data.json new file mode 100644 index 000000000..4a0893540 --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/specs/stacked-area-by-group.data.json @@ -0,0 +1,842 @@ +[ +{ +"t": "2026-01-01", +"grp": "Housing", +"cat": "Rent", +"acc": "ING", +"v": 325 +}, +{ +"t": "2026-01-01", +"grp": "Housing", +"cat": "Rent", +"acc": "Revolut", +"v": 300 +}, +{ +"t": "2026-01-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "ING", +"v": 435 +}, +{ +"t": "2026-01-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "Revolut", +"v": 410 +}, +{ +"t": "2026-01-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "ING", +"v": 325 +}, +{ +"t": "2026-01-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "Revolut", +"v": 300 +}, +{ +"t": "2026-01-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "ING", +"v": 435 +}, +{ +"t": "2026-01-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "Revolut", +"v": 410 +}, +{ +"t": "2026-01-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "ING", +"v": 325 +}, +{ +"t": "2026-01-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "Revolut", +"v": 300 +}, +{ +"t": "2026-02-01", +"grp": "Housing", +"cat": "Rent", +"acc": "ING", +"v": 365 +}, +{ +"t": "2026-02-01", +"grp": "Housing", +"cat": "Rent", +"acc": "Revolut", +"v": 340 +}, +{ +"t": "2026-02-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "ING", +"v": 475 +}, +{ +"t": "2026-02-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "Revolut", +"v": 450 +}, +{ +"t": "2026-02-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "ING", +"v": 365 +}, +{ +"t": "2026-02-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "Revolut", +"v": 340 +}, +{ +"t": "2026-02-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "ING", +"v": 475 +}, +{ +"t": "2026-02-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "Revolut", +"v": 450 +}, +{ +"t": "2026-02-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "ING", +"v": 365 +}, +{ +"t": "2026-02-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "Revolut", +"v": 340 +}, +{ +"t": "2026-03-01", +"grp": "Housing", +"cat": "Rent", +"acc": "ING", +"v": 405 +}, +{ +"t": "2026-03-01", +"grp": "Housing", +"cat": "Rent", +"acc": "Revolut", +"v": 380 +}, +{ +"t": "2026-03-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "ING", +"v": 515 +}, +{ +"t": "2026-03-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "Revolut", +"v": 490 +}, +{ +"t": "2026-03-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "ING", +"v": 405 +}, +{ +"t": "2026-03-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "Revolut", +"v": 380 +}, +{ +"t": "2026-03-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "ING", +"v": 515 +}, +{ +"t": "2026-03-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "Revolut", +"v": 490 +}, +{ +"t": "2026-03-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "ING", +"v": 405 +}, +{ +"t": "2026-03-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "Revolut", +"v": 380 +}, +{ +"t": "2026-04-01", +"grp": "Housing", +"cat": "Rent", +"acc": "ING", +"v": 445 +}, +{ +"t": "2026-04-01", +"grp": "Housing", +"cat": "Rent", +"acc": "Revolut", +"v": 420 +}, +{ +"t": "2026-04-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "ING", +"v": 395 +}, +{ +"t": "2026-04-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "Revolut", +"v": 370 +}, +{ +"t": "2026-04-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "ING", +"v": 445 +}, +{ +"t": "2026-04-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "Revolut", +"v": 420 +}, +{ +"t": "2026-04-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "ING", +"v": 395 +}, +{ +"t": "2026-04-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "Revolut", +"v": 370 +}, +{ +"t": "2026-04-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "ING", +"v": 445 +}, +{ +"t": "2026-04-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "Revolut", +"v": 420 +}, +{ +"t": "2026-05-01", +"grp": "Housing", +"cat": "Rent", +"acc": "ING", +"v": 325 +}, +{ +"t": "2026-05-01", +"grp": "Housing", +"cat": "Rent", +"acc": "Revolut", +"v": 300 +}, +{ +"t": "2026-05-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "ING", +"v": 435 +}, +{ +"t": "2026-05-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "Revolut", +"v": 410 +}, +{ +"t": "2026-05-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "ING", +"v": 325 +}, +{ +"t": "2026-05-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "Revolut", +"v": 300 +}, +{ +"t": "2026-05-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "ING", +"v": 435 +}, +{ +"t": "2026-05-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "Revolut", +"v": 410 +}, +{ +"t": "2026-05-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "ING", +"v": 325 +}, +{ +"t": "2026-05-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "Revolut", +"v": 300 +}, +{ +"t": "2026-06-01", +"grp": "Housing", +"cat": "Rent", +"acc": "ING", +"v": 365 +}, +{ +"t": "2026-06-01", +"grp": "Housing", +"cat": "Rent", +"acc": "Revolut", +"v": 340 +}, +{ +"t": "2026-06-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "ING", +"v": 475 +}, +{ +"t": "2026-06-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "Revolut", +"v": 450 +}, +{ +"t": "2026-06-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "ING", +"v": 365 +}, +{ +"t": "2026-06-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "Revolut", +"v": 340 +}, +{ +"t": "2026-06-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "ING", +"v": 475 +}, +{ +"t": "2026-06-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "Revolut", +"v": 450 +}, +{ +"t": "2026-06-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "ING", +"v": 365 +}, +{ +"t": "2026-06-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "Revolut", +"v": 340 +}, +{ +"t": "2026-07-01", +"grp": "Housing", +"cat": "Rent", +"acc": "ING", +"v": 405 +}, +{ +"t": "2026-07-01", +"grp": "Housing", +"cat": "Rent", +"acc": "Revolut", +"v": 380 +}, +{ +"t": "2026-07-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "ING", +"v": 515 +}, +{ +"t": "2026-07-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "Revolut", +"v": 490 +}, +{ +"t": "2026-07-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "ING", +"v": 405 +}, +{ +"t": "2026-07-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "Revolut", +"v": 380 +}, +{ +"t": "2026-07-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "ING", +"v": 515 +}, +{ +"t": "2026-07-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "Revolut", +"v": 490 +}, +{ +"t": "2026-07-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "ING", +"v": 405 +}, +{ +"t": "2026-07-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "Revolut", +"v": 380 +}, +{ +"t": "2026-08-01", +"grp": "Housing", +"cat": "Rent", +"acc": "ING", +"v": 445 +}, +{ +"t": "2026-08-01", +"grp": "Housing", +"cat": "Rent", +"acc": "Revolut", +"v": 420 +}, +{ +"t": "2026-08-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "ING", +"v": 395 +}, +{ +"t": "2026-08-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "Revolut", +"v": 370 +}, +{ +"t": "2026-08-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "ING", +"v": 445 +}, +{ +"t": "2026-08-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "Revolut", +"v": 420 +}, +{ +"t": "2026-08-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "ING", +"v": 395 +}, +{ +"t": "2026-08-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "Revolut", +"v": 370 +}, +{ +"t": "2026-08-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "ING", +"v": 445 +}, +{ +"t": "2026-08-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "Revolut", +"v": 420 +}, +{ +"t": "2026-09-01", +"grp": "Housing", +"cat": "Rent", +"acc": "ING", +"v": 325 +}, +{ +"t": "2026-09-01", +"grp": "Housing", +"cat": "Rent", +"acc": "Revolut", +"v": 300 +}, +{ +"t": "2026-09-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "ING", +"v": 435 +}, +{ +"t": "2026-09-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "Revolut", +"v": 410 +}, +{ +"t": "2026-09-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "ING", +"v": 325 +}, +{ +"t": "2026-09-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "Revolut", +"v": 300 +}, +{ +"t": "2026-09-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "ING", +"v": 435 +}, +{ +"t": "2026-09-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "Revolut", +"v": 410 +}, +{ +"t": "2026-09-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "ING", +"v": 325 +}, +{ +"t": "2026-09-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "Revolut", +"v": 300 +}, +{ +"t": "2026-10-01", +"grp": "Housing", +"cat": "Rent", +"acc": "ING", +"v": 365 +}, +{ +"t": "2026-10-01", +"grp": "Housing", +"cat": "Rent", +"acc": "Revolut", +"v": 340 +}, +{ +"t": "2026-10-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "ING", +"v": 475 +}, +{ +"t": "2026-10-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "Revolut", +"v": 450 +}, +{ +"t": "2026-10-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "ING", +"v": 365 +}, +{ +"t": "2026-10-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "Revolut", +"v": 340 +}, +{ +"t": "2026-10-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "ING", +"v": 475 +}, +{ +"t": "2026-10-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "Revolut", +"v": 450 +}, +{ +"t": "2026-10-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "ING", +"v": 365 +}, +{ +"t": "2026-10-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "Revolut", +"v": 340 +}, +{ +"t": "2026-11-01", +"grp": "Housing", +"cat": "Rent", +"acc": "ING", +"v": 405 +}, +{ +"t": "2026-11-01", +"grp": "Housing", +"cat": "Rent", +"acc": "Revolut", +"v": 380 +}, +{ +"t": "2026-11-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "ING", +"v": 515 +}, +{ +"t": "2026-11-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "Revolut", +"v": 490 +}, +{ +"t": "2026-11-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "ING", +"v": 405 +}, +{ +"t": "2026-11-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "Revolut", +"v": 380 +}, +{ +"t": "2026-11-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "ING", +"v": 515 +}, +{ +"t": "2026-11-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "Revolut", +"v": 490 +}, +{ +"t": "2026-11-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "ING", +"v": 405 +}, +{ +"t": "2026-11-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "Revolut", +"v": 380 +}, +{ +"t": "2026-12-01", +"grp": "Housing", +"cat": "Rent", +"acc": "ING", +"v": 445 +}, +{ +"t": "2026-12-01", +"grp": "Housing", +"cat": "Rent", +"acc": "Revolut", +"v": 420 +}, +{ +"t": "2026-12-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "ING", +"v": 395 +}, +{ +"t": "2026-12-01", +"grp": "Housing", +"cat": "Utilities", +"acc": "Revolut", +"v": 370 +}, +{ +"t": "2026-12-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "ING", +"v": 445 +}, +{ +"t": "2026-12-01", +"grp": "Daily living", +"cat": "Groceries", +"acc": "Revolut", +"v": 420 +}, +{ +"t": "2026-12-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "ING", +"v": 395 +}, +{ +"t": "2026-12-01", +"grp": "Daily living", +"cat": "Transport", +"acc": "Revolut", +"v": 370 +}, +{ +"t": "2026-12-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "ING", +"v": 445 +}, +{ +"t": "2026-12-01", +"grp": "Lifestyle", +"cat": "Shopping", +"acc": "Revolut", +"v": 420 +} +] \ No newline at end of file diff --git a/.claude/skills/packs/mendix-vega-charts/specs/stacked-area-by-group.json b/.claude/skills/packs/mendix-vega-charts/specs/stacked-area-by-group.json new file mode 100644 index 000000000..9ca628967 --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/specs/stacked-area-by-group.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://vega.github.io/schema/vega-lite/v6.json", + "data": {"name": "table"}, + "background": null, + "width": "container", + "height": 280, + "config": {"view": {"stroke": null}}, + "layer": [ + { + "description": "Bands by group, detailed by category so each keeps its own band while the colour stays at group level. Thirteen hues is a puzzle; five is a legend you can hold in your head.", + "mark": {"type": "area", "interpolate": "linear", "opacity": 0.9}, + "encoding": { + "y": {"field": "v", "type": "quantitative", "aggregate": "sum", "stack": "zero", + "title": null, "axis": {"format": ",.0f", "tickCount": 4}}, + "color": {"field": "grp", "type": "nominal", "title": null, + "scale": {"scheme": "tableau10"}}, + "detail": {"field": "cat", "type": "nominal"}, + "tooltip": [ + {"field": "cat", "type": "nominal", "title": "Category"}, + {"field": "t", "type": "temporal", "title": "Month", "format": "%b %Y"}, + {"field": "v", "type": "quantitative", "aggregate": "sum", "title": "Amount", "format": ",.2f"} + ] + } + } + ], + "encoding": {"x": {"field": "t", "type": "temporal", "title": null, + "axis": {"format": "%b %y", "grid": false}}} +} diff --git a/.claude/skills/packs/mendix-vega-charts/widget/package.json b/.claude/skills/packs/mendix-vega-charts/widget/package.json new file mode 100644 index 000000000..a8e4d98bc --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/widget/package.json @@ -0,0 +1,28 @@ +{ + "name": "vegachart", + "widgetName": "VegaChart", + "version": "1.0.0", + "description": "Renders a Vega-Lite or Vega specification, with the spec and the data supplied separately.", + "license": "MIT", + "private": true, + "config": { + "projectPath": "{{PROJECT_PATH}}", + "mendixHost": "http://localhost:8080", + "developmentPort": 3000 + }, + "packagePath": "{{NAMESPACE}}.widget.web", + "scripts": { + "build": "pluggable-widgets-tools build:web", + "lint": "pluggable-widgets-tools lint", + "start": "pluggable-widgets-tools start:server" + }, + "dependencies": { + "vega": "6.3.1", + "vega-lite": "6.4.3", + "vega-embed": "7.1.0" + }, + "devDependencies": { + "@mendix/pluggable-widgets-tools": "11.12.1", + "@types/big.js": "^6.2.2" + } +} diff --git a/.claude/skills/packs/mendix-vega-charts/widget/src/VegaChart.tsx b/.claude/skills/packs/mendix-vega-charts/widget/src/VegaChart.tsx new file mode 100644 index 000000000..822bb1fe7 --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/widget/src/VegaChart.tsx @@ -0,0 +1,163 @@ +import { useEffect, useMemo, useRef, useState, ReactElement } from "react"; +import embed, { Result as EmbedResult, VisualizationSpec } from "vega-embed"; + +import { VegaChartContainerProps } from "../typings/VegaChartProps"; + +// Not decoration: a spec using `"width": "container"` measures this element, and +// without the width rule below it measures zero and the chart draws nothing — +// silently, since an empty chart is not an error. The stylesheet only reaches +// the bundle if it is imported here. +import "./ui/VegaChart.css"; + +/** + * Parse a JSON string, returning the message rather than throwing. + * + * Both the spec and the data arrive as text — the spec authored by hand, the + * data built by a microflow — so a syntax error in either is a normal thing to + * hit while authoring, not an exceptional one. Showing where it broke beats a + * blank chart and a console trace. + */ +function parseJson(raw: string, label: string): { value?: T; error?: string } { + try { + return { value: JSON.parse(raw) as T }; + } catch (e) { + return { error: `${label} is not valid JSON: ${(e as Error).message}` }; + } +} + +/** + * A clicked mark's datum, reduced to what the model would recognise. + * + * Vega hangs its own bookkeeping on every datum — a numeric `_vgsid_`, and for + * aggregated marks the whole source array under `_source_`. Passing that back + * would be noise at best and, in the aggregate case, the entire dataset in a + * string attribute. Only own scalar fields survive. + */ +function cleanDatum(datum: Record): Record { + const out: Record = {}; + for (const key of Object.keys(datum)) { + if (key.startsWith("_")) { + continue; + } + const value = datum[key]; + const type = typeof value; + if (value === null || type === "string" || type === "number" || type === "boolean") { + out[key] = value; + } else if (value instanceof Date) { + out[key] = value.toISOString().slice(0, 10); + } + } + return out; +} + +export function VegaChart(props: VegaChartContainerProps): ReactElement { + const { spec, chartData, datasetName, chartHeight, renderer, showActions, selection, onClick } = props; + const hostRef = useRef(null); + const viewRef = useRef(null); + const [error, setError] = useState(); + + // The click handler is attached once per embed but reads the current props, + // so it is held in a ref rather than captured — re-embedding on every render + // just to refresh a callback would rebuild the whole scenegraph. + const clickRef = useRef({ selection, onClick }); + clickRef.current = { selection, onClick }; + + const dataValue = chartData?.status === "available" ? chartData.value : undefined; + + // The spec is static and the data is not, so they are parsed apart. Only the + // data changes between renders, and re-parsing a spec on every model update + // would be wasted work. + const parsedSpec = useMemo(() => parseJson(spec, "Specification"), [spec]); + const parsedData = useMemo( + () => (dataValue ? parseJson(dataValue, "Data") : { value: undefined }), + [dataValue] + ); + + // Fold the data into the spec. A named dataset goes into `datasets`, which is + // how Vega-Lite expects a spec to reference data it does not carry itself; + // without a name the top-level `data` is replaced instead. + const resolvedSpec = useMemo(() => { + if (!parsedSpec.value) { + return undefined; + } + if (!parsedData.value) { + return parsedSpec.value; + } + const next = { ...(parsedSpec.value as Record) }; + if (datasetName) { + next.datasets = { ...((next.datasets as object) ?? {}), [datasetName]: parsedData.value }; + } else { + next.data = { values: parsedData.value }; + } + return next as VisualizationSpec; + }, [parsedSpec.value, parsedData.value, datasetName]); + + useEffect(() => { + const message = parsedSpec.error ?? parsedData.error; + if (message) { + setError(message); + return; + } + if (!hostRef.current || !resolvedSpec) { + return; + } + + let disposed = false; + // vega-embed decides between Vega and Vega-Lite from the spec's own + // $schema, so one widget serves both languages with no switch here. + embed(hostRef.current, resolvedSpec, { + actions: showActions, + renderer, + // The app supplies its own type scale and palette; letting Vega apply + // a theme on top would fight it. + config: { background: "transparent" } + }) + .then(result => { + if (disposed) { + result.finalize(); + return; + } + viewRef.current?.finalize(); + viewRef.current = result; + setError(undefined); + + // Clicks on the chart background arrive with no item, and clicks + // on axes and legends arrive with an item that has no datum; + // neither is a selection. A chart with no onClick configured + // ignores clicks entirely rather than writing a value nothing + // reads. + result.view.addEventListener("click", (_event, item) => { + const { selection: sel, onClick: act } = clickRef.current; + if (!act?.canExecute || !item?.datum) { + return; + } + sel?.setValue(JSON.stringify(cleanDatum(item.datum as Record))); + act.execute(); + }); + }) + .catch((e: Error) => !disposed && setError(e.message)); + + return () => { + disposed = true; + }; + }, [resolvedSpec, renderer, showActions, parsedSpec.error, parsedData.error]); + + // Finalize on unmount only. Vega registers listeners and, with the canvas + // renderer, holds a backing surface; dropping the node without finalizing + // leaks both. + useEffect(() => () => viewRef.current?.finalize(), []); + + if (error) { + return ( +
+ {error} +
+ ); + } + + // Zero means "as tall as it comes out". A chart whose height is decided by + // its data — a facet row per category, a legend entry per series — has no + // number the page can be told in advance, and a fixed container silently + // stops matching the moment the data grows. + return
0 ? chartHeight : undefined }} />; +} diff --git a/.claude/skills/packs/mendix-vega-charts/widget/src/VegaChart.xml b/.claude/skills/packs/mendix-vega-charts/widget/src/VegaChart.xml new file mode 100644 index 000000000..dbd237bac --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/widget/src/VegaChart.xml @@ -0,0 +1,60 @@ + + + Vega Chart + Renders a Vega-Lite or Vega specification. The spec is authored once and held here; only the data comes from the model. + + + + + Specification + Vega-Lite or Vega JSON. Which language is used is decided by the spec's own $schema, so both are accepted here. Any "data" in the spec is replaced by the Data property below when that is set. + + + Data + A JSON array of row objects, bound to the spec's named dataset (or to its top-level data when no name is given). Kept separate from the spec so the model never has to generate layout. + + + + + + Dataset name + The name the spec uses to refer to the data (Vega-Lite: {"data": {"name": "table"}}). Leave empty to replace the spec's top-level data instead. + + + + + Selection + Written with the clicked mark's datum as JSON. Only the datum's own scalar fields are included — Vega's internal bookkeeping is stripped, so what arrives is the row the model emitted. + + + + + + On click + Runs after the selection attribute is written. Leave empty to make the chart read-only; clicks are then ignored entirely rather than being written and discarded. + + + + + Height (px) + Height of the chart container. Zero lets the container take the height the chart renders at, which is what a chart sized by its data needs — a facet row per category or a legend entry per series has no height the page can know in advance. + + + Renderer + SVG keeps marks selectable and styleable by CSS; canvas is faster for very dense charts. + + SVG + Canvas + + + + Show actions menu + Vega's own export / view-source menu. Off by default so the chart carries no chrome of its own. + + + + diff --git a/.claude/skills/packs/mendix-vega-charts/widget/src/package.xml b/.claude/skills/packs/mendix-vega-charts/widget/src/package.xml new file mode 100644 index 000000000..aad343ad5 --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/widget/src/package.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/.claude/skills/packs/mendix-vega-charts/widget/src/ui/VegaChart.css b/.claude/skills/packs/mendix-vega-charts/widget/src/ui/VegaChart.css new file mode 100644 index 000000000..e76b710c8 --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/widget/src/ui/VegaChart.css @@ -0,0 +1,36 @@ +.vega-chart { + /* A chart wired to onClick should look clickable. */ + + width: 100%; +} + +.vega-chart .vega-embed { + width: 100%; +} + +/* Vega sets its own font on text marks; the app's type scale should win. */ +.vega-chart text { + font-family: inherit; +} + +.vega-chart-error { + padding: 12px 14px; + font-family: var(--mxt-font-mono, ui-monospace, monospace); + font-size: 12px; + line-height: 1.5; + color: #a8321e; + background: rgba(168, 50, 30, 0.06); + border: 1px solid rgba(168, 50, 30, 0.25); + border-radius: 2px; + white-space: pre-wrap; +} + +/* Marks only — the background and axes are not selections, so the cursor + should not promise that they are. */ +.vega-chart-clickable .mark-symbol path, +.vega-chart-clickable .mark-rect path, +.vega-chart-clickable .mark-arc path, +.vega-chart-clickable .mark-area path, +.vega-chart-clickable .mark-rule path { + cursor: pointer; +} diff --git a/.claude/skills/packs/mendix-vega-charts/widget/tsconfig.json b/.claude/skills/packs/mendix-vega-charts/widget/tsconfig.json new file mode 100644 index 000000000..b342bbd2d --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/widget/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "./node_modules/@mendix/pluggable-widgets-tools/configs/tsconfig.base.json", + "compilerOptions": { + // vega-embed 7 exposes its types only through package.json "exports", + // which the base config's node10 resolution does not read. Rollup itself + // resolves the package fine — this is a type-resolution fix, not a + // bundling one. + "moduleResolution": "bundler" + }, + "include": ["./src", "./typings"] +} diff --git a/.claude/skills/packs/mendix-vega-charts/widget/typings/VegaChartProps.d.ts b/.claude/skills/packs/mendix-vega-charts/widget/typings/VegaChartProps.d.ts new file mode 100644 index 000000000..022eaff49 --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/widget/typings/VegaChartProps.d.ts @@ -0,0 +1,45 @@ +/** + * This file was generated from VegaChart.xml + * WARNING: All changes made to this file will be overwritten + * @author Mendix Widgets Framework Team + */ +import { ActionValue, EditableValue } from "mendix"; +import { CSSProperties } from "react"; + +export type RendererEnum = "svg" | "canvas"; + +export interface VegaChartContainerProps { + name: string; + class: string; + style?: CSSProperties; + tabIndex?: number; + spec: string; + chartData?: EditableValue; + datasetName: string; + selection?: EditableValue; + onClick?: ActionValue; + chartHeight: number; + renderer: RendererEnum; + showActions: boolean; +} + +export interface VegaChartPreviewProps { + /** + * @deprecated Deprecated since version 9.18.0. Please use class property instead. + */ + className: string; + class: string; + style: string; + styleObject?: CSSProperties; + readOnly: boolean; + renderMode: "design" | "xray" | "structure"; + translate: (text: string) => string; + spec: string; + chartData: string; + datasetName: string; + selection: string; + onClick: {} | null; + chartHeight: number | null; + renderer: RendererEnum; + showActions: boolean; +} diff --git a/cmd/mxcli/cmd_skill.go b/cmd/mxcli/cmd_skill.go index 099d9c973..a04508a8a 100644 --- a/cmd/mxcli/cmd_skill.go +++ b/cmd/mxcli/cmd_skill.go @@ -13,7 +13,11 @@ import ( "github.com/mendixlabs/mxcli/cmd/mxcli/skillpack" ) -var skillPackDir string +var ( + skillPackDir string + skillPackNamespace string + skillPackProject string +) // packsFS returns the embedded packs rooted at the pack directory, so callers // see `/SKILL.md` rather than `skillpacks//SKILL.md`. @@ -105,7 +109,32 @@ var skillAddCmd = &cobra.Command{ if err := os.MkdirAll(dir, 0o755); err != nil { return err } - res, err := skillpack.Install(fsys, pack.Name, dir) + opts := skillpack.Options{} + var ns string + if pack.NeedsNamespace() { + ns, err = resolveNamespace() + if err != nil { + return err + } + // The widget source lands in //widget/, so the build's + // output target is relative to there. + // + // Both sides are made absolute first. filepath.Rel refuses to mix a + // relative and an absolute path, and the fallback is an ABSOLUTE + // projectPath baked into a package.json that gets committed — which + // works on exactly one machine and fails silently everywhere else. + widgetDir, err1 := filepath.Abs(filepath.Join(dir, pack.Name, "widget")) + projDir, err2 := filepath.Abs(projectDirForPack()) + rel := projDir + if err1 == nil && err2 == nil { + if r, relErr := filepath.Rel(widgetDir, projDir); relErr == nil { + rel = r + } + } + opts.Vars = skillpack.Vars(ns, filepath.ToSlash(rel)) + } + + res, err := skillpack.InstallWith(fsys, pack.Name, dir, opts) if err != nil { return err } @@ -122,6 +151,21 @@ var skillAddCmd = &cobra.Command{ fmt.Println() } + if ns != "" { + fmt.Printf("\nNamespace: %s\n", ns) + fmt.Printf(" widget id: %s\n", skillpack.WidgetID(ns, "vegachart.VegaChart")) + fmt.Println(" Set before the build, so the id is right the first time a page references it.") + fmt.Println(" Renaming later means re-applying every page that carries the widget.") + fmt.Printf("\nBuild it:\n cd %s && npm ci && npm run build\n", + filepath.Join(dir, pack.Name, "widget")) + fmt.Println(" The .mpk lands in the project's widgets/ — commit it, or every other") + fmt.Println(" clone of the repo references a widget nobody has.") + // Without this, the first page authored against the widget fails with + // "no definition for widget ...", which reads as a packaging problem + // rather than a step nobody was told about. + fmt.Println("\nThen let mxcli see it:\n mxcli widget init -p .mpr") + } + // Copying the pack never touches the model. Anything that would is // reported as a next step the user runs deliberately — a documentation // install that silently added Java actions to the .mpr would be exactly @@ -202,6 +246,47 @@ var skillUpgradeCmd = &cobra.Command{ func init() { skillCmd.PersistentFlags().StringVar(&skillPackDir, "dir", "", "Install packs here instead of ./.claude/skills") + skillAddCmd.Flags().StringVar(&skillPackNamespace, "namespace", "", + "Widget namespace for packs that ship a widget (default: derived from the project name)") + skillAddCmd.Flags().StringVarP(&skillPackProject, "project", "p", "", + "Path to the .mpr the pack is being installed for") skillCmd.AddCommand(skillListCmd, skillAddCmd, skillRemoveCmd, skillUpgradeCmd) rootCmd.AddCommand(skillCmd) } + +// resolveNamespace decides the widget namespace for a pack that ships one. +// +// Explicit --namespace wins. Otherwise it comes from the project name, which is +// a default rather than something to apply silently — the caller prints it, so a +// project called App1112 does not quietly become the vendor prefix of a widget +// that ends up somewhere else. +func resolveNamespace() (string, error) { + if skillPackNamespace != "" { + return skillpack.NormalizeNamespace(skillPackNamespace) + } + mpr := skillPackProject + if mpr == "" { + mpr = findMprFile(".") + } + if mpr == "" { + return "", fmt.Errorf("this pack ships a widget, whose id must carry your namespace.\n" + + "No .mpr found here to derive one from — pass --namespace acme (or -p .mpr)") + } + return skillpack.NamespaceFromProject(mpr) +} + +// projectDirForPack is the directory the built widget package should land in, +// which is the directory holding the .mpr. +func projectDirForPack() string { + mpr := skillPackProject + if mpr == "" { + mpr = findMprFile(".") + } + if mpr == "" { + return "." + } + if abs, err := filepath.Abs(filepath.Dir(mpr)); err == nil { + return abs + } + return filepath.Dir(mpr) +} diff --git a/cmd/mxcli/skillpack/namespace.go b/cmd/mxcli/skillpack/namespace.go new file mode 100644 index 000000000..2f9ccc1bb --- /dev/null +++ b/cmd/mxcli/skillpack/namespace.go @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: Apache-2.0 + +package skillpack + +import ( + "fmt" + "path/filepath" + "regexp" + "strings" +) + +// A Mendix pluggable widget id looks like `acme.widget.web.vegachart.VegaChart`. +// The first segment identifies whoever built it, and it is the only part a +// consuming project has any business changing — `widget.web` is convention and +// the tail comes from the widget itself. +// +// Getting this wrong is not a build error. Two apps whose widgets share an id +// are two apps claiming the same widget, and what you see is a widget that +// resolves to somebody else's build. + +var namespaceInvalid = regexp.MustCompile(`[^a-z0-9]+`) + +// NormalizeNamespace turns a human-supplied name into a legal id segment: +// lowercase, alphanumeric, no leading digit. +// +// A leading digit is rejected rather than silently prefixed, because a namespace +// the user did not choose is exactly as wrong as one that does not fit — and +// they would find out at build time with the id already baked into pages. +func NormalizeNamespace(in string) (string, error) { + s := namespaceInvalid.ReplaceAllString(strings.ToLower(strings.TrimSpace(in)), "") + if s == "" { + return "", fmt.Errorf("namespace %q has no usable characters; "+ + "pass --namespace with a name like 'acme'", in) + } + if s[0] >= '0' && s[0] <= '9' { + return "", fmt.Errorf("namespace %q starts with a digit, which a widget id cannot; "+ + "pass --namespace with a name starting with a letter", in) + } + return s, nil +} + +// NamespaceFromProject derives a namespace from the project's .mpr filename. +// +// This is a default, not a guess to be trusted silently: callers print what was +// derived so a project called `App1112` does not quietly become the vendor +// prefix of a widget somebody ships elsewhere. +func NamespaceFromProject(mprPath string) (string, error) { + base := filepath.Base(mprPath) + base = strings.TrimSuffix(base, filepath.Ext(base)) + if base == "" || base == "." { + return "", fmt.Errorf("cannot derive a namespace from %q; pass --namespace", mprPath) + } + return NormalizeNamespace(base) +} + +// Vars builds the substitution set for a pack installed into a project. +// +// projectPath is written into the widget's package.json as the build's output +// target, relative to where the widget source lands. +func Vars(namespace, projectPath string) map[string]string { + return map[string]string{ + "NAMESPACE": namespace, + "NAMESPACE_PATH": strings.ReplaceAll(namespace, ".", "/"), + "PROJECT_PATH": projectPath, + } +} + +// WidgetID is the full pluggable-widget id a pack's widget will carry once +// installed under the given namespace. Printed on install so the id a page must +// reference is never something the user has to reconstruct by hand. +func WidgetID(namespace, tail string) string { + return namespace + ".widget.web." + tail +} diff --git a/cmd/mxcli/skillpack/rewrite_test.go b/cmd/mxcli/skillpack/rewrite_test.go new file mode 100644 index 000000000..a37fcdf19 --- /dev/null +++ b/cmd/mxcli/skillpack/rewrite_test.go @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: Apache-2.0 + +package skillpack + +import ( + "os" + "path/filepath" + "strings" + "testing" + "testing/fstest" +) + +const nsManifest = `name: ns-pack +version: 1.0.0 +rewrite: + files: + - widget/package.json + - widget/src/VegaChart.xml +` + +func nsFS() fstest.MapFS { + return fstest.MapFS{ + "ns-pack/pack.yaml": {Data: []byte(nsManifest)}, + "ns-pack/SKILL.md": {Data: []byte("# ns\n")}, + "ns-pack/widget/package.json": {Data: []byte( + `{"packagePath":"{{NAMESPACE}}.widget.web","config":{"projectPath":"{{PROJECT_PATH}}"}}`)}, + "ns-pack/widget/src/VegaChart.xml": {Data: []byte( + ``)}, + // Not listed under rewrite.files: a spec that happens to contain brace + // syntax must come through byte-for-byte. + "ns-pack/specs/bar.json": {Data: []byte(`{"mark":"bar","text":"{{NAMESPACE}} is not a token here"}`)}, + } +} + +// TestInstallSubstitutesNamespace is the headline behaviour: the widget id a +// project ends up with must be the project's own, in every file that carries it. +func TestInstallSubstitutesNamespace(t *testing.T) { + dest := t.TempDir() + _, err := InstallWith(nsFS(), "ns-pack", dest, Options{Vars: Vars("acme", "../../..")}) + if err != nil { + t.Fatalf("InstallWith: %v", err) + } + + pkg := readFile(t, dest, "ns-pack/widget/package.json") + if !strings.Contains(pkg, `"packagePath":"acme.widget.web"`) { + t.Errorf("packagePath not substituted: %s", pkg) + } + if !strings.Contains(pkg, `"projectPath":"../../.."`) { + t.Errorf("projectPath not substituted: %s", pkg) + } + xml := readFile(t, dest, "ns-pack/widget/src/VegaChart.xml") + if !strings.Contains(xml, `id="acme.widget.web.vegachart.VegaChart"`) { + t.Errorf("widget id not substituted: %s", xml) + } +} + +// TestRewriteTouchesOnlyDeclaredFiles — substitution is a whitelist, not a scan. +// A pack ships megabytes of built JS and spec JSON, and a blind replace across +// all of it would rewrite content that merely looks like a token. +func TestRewriteTouchesOnlyDeclaredFiles(t *testing.T) { + dest := t.TempDir() + if _, err := InstallWith(nsFS(), "ns-pack", dest, Options{Vars: Vars("acme", ".")}); err != nil { + t.Fatalf("InstallWith: %v", err) + } + spec := readFile(t, dest, "ns-pack/specs/bar.json") + if !strings.Contains(spec, "{{NAMESPACE}} is not a token here") { + t.Errorf("an undeclared file was rewritten: %s", spec) + } +} + +// TestInstallRefusesWithoutVars — a pack whose id must carry the destination's +// namespace cannot be installed without one. Silently shipping the placeholder +// (or the pack author's own namespace) is the failure this design exists to make +// impossible. +func TestInstallRefusesWithoutVars(t *testing.T) { + dest := t.TempDir() + if _, err := Install(nsFS(), "ns-pack", dest); err == nil { + t.Error("a pack needing a namespace installed without one") + } +} + +// TestInstallRefusesUnknownToken — a token the caller has no value for must stop +// the install, not go out unsubstituted. +func TestInstallRefusesUnknownToken(t *testing.T) { + fsys := nsFS() + fsys["ns-pack/widget/src/VegaChart.xml"] = &fstest.MapFile{ + Data: []byte(``)} + dest := t.TempDir() + _, err := InstallWith(fsys, "ns-pack", dest, Options{Vars: Vars("acme", ".")}) + if err == nil || !strings.Contains(err.Error(), "UNDECLARED") { + t.Errorf("expected a refusal naming UNDECLARED, got %v", err) + } +} + +// TestInstallRefusesStaleManifest covers both directions of drift: a declared +// file that carries no token (the file changed under the manifest), and a +// declared file the pack does not ship at all. Either one means a file somebody +// intended to rewrite went out untouched. +func TestInstallRefusesStaleManifest(t *testing.T) { + t.Run("declared file has no token", func(t *testing.T) { + fsys := nsFS() + fsys["ns-pack/widget/src/VegaChart.xml"] = &fstest.MapFile{Data: []byte(``)} + if _, err := InstallWith(fsys, "ns-pack", t.TempDir(), Options{Vars: Vars("acme", ".")}); err == nil { + t.Error("a declared file with no token was accepted") + } + }) + t.Run("declared file is not shipped", func(t *testing.T) { + fsys := nsFS() + delete(fsys, "ns-pack/widget/src/VegaChart.xml") + if _, err := InstallWith(fsys, "ns-pack", t.TempDir(), Options{Vars: Vars("acme", ".")}); err == nil { + t.Error("a manifest naming a file the pack does not ship was accepted") + } + }) +} + +// TestUpgradeKeepsTheInstalledNamespace — re-deriving on upgrade would change a +// widget id when the project is renamed, and every page referencing it would be +// pointing at a widget that no longer exists under that name. +func TestUpgradeKeepsTheInstalledNamespace(t *testing.T) { + dest := t.TempDir() + if _, err := InstallWith(nsFS(), "ns-pack", dest, Options{Vars: Vars("acme", ".")}); err != nil { + t.Fatalf("first install: %v", err) + } + // An upgrade supplies no vars — it must recover them from the lock. + if _, err := Install(nsFS(), "ns-pack", dest); err != nil { + t.Fatalf("upgrade: %v", err) + } + xml := readFile(t, dest, "ns-pack/widget/src/VegaChart.xml") + if !strings.Contains(xml, `id="acme.`) { + t.Errorf("upgrade lost the installed namespace: %s", xml) + } +} + +// TestLockSurvivesPrune — the lock is written by the install, not shipped by the +// pack, so the prune that removes everything unshipped must not take it. +func TestLockSurvivesPrune(t *testing.T) { + dest := t.TempDir() + if _, err := InstallWith(nsFS(), "ns-pack", dest, Options{Vars: Vars("acme", ".")}); err != nil { + t.Fatalf("install: %v", err) + } + if _, err := os.Stat(filepath.Join(dest, "ns-pack", LockName)); err != nil { + t.Fatalf("lock missing after install: %v", err) + } + if _, err := Install(nsFS(), "ns-pack", dest); err != nil { + t.Fatalf("second install: %v", err) + } + if _, err := os.Stat(filepath.Join(dest, "ns-pack", LockName)); err != nil { + t.Errorf("prune removed the lock: %v", err) + } +} + +func TestNormalizeNamespace(t *testing.T) { + ok := map[string]string{ + "acme": "acme", + "Acme": "acme", + "My App": "myapp", + "my-app_1112": "myapp1112", + "App1112": "app1112", + } + for in, want := range ok { + got, err := NormalizeNamespace(in) + if err != nil { + t.Errorf("NormalizeNamespace(%q): %v", in, err) + continue + } + if got != want { + t.Errorf("NormalizeNamespace(%q) = %q, want %q", in, got, want) + } + } + // A leading digit is rejected rather than silently prefixed: a namespace the + // user did not choose is as wrong as one that does not fit, and they would + // find out with the id already baked into pages. + for _, bad := range []string{"", " ", "123", "1app", "---"} { + if got, err := NormalizeNamespace(bad); err == nil { + t.Errorf("NormalizeNamespace(%q) = %q, want an error", bad, got) + } + } +} + +func TestNamespaceFromProject(t *testing.T) { + cases := map[string]string{ + "App1112.mpr": "app1112", + "/tmp/wd/My-App.mpr": "myapp", + "./projects/Ledger.mpr": "ledger", + } + for in, want := range cases { + got, err := NamespaceFromProject(in) + if err != nil { + t.Errorf("NamespaceFromProject(%q): %v", in, err) + continue + } + if got != want { + t.Errorf("NamespaceFromProject(%q) = %q, want %q", in, got, want) + } + } +} + +func TestWidgetID(t *testing.T) { + if got := WidgetID("acme", "vegachart.VegaChart"); got != "acme.widget.web.vegachart.VegaChart" { + t.Errorf("WidgetID = %q", got) + } +} + +func readFile(t *testing.T, dest, rel string) string { + t.Helper() + b, err := os.ReadFile(filepath.Join(dest, filepath.FromSlash(rel))) + if err != nil { + t.Fatalf("reading %s: %v", rel, err) + } + return string(b) +} diff --git a/cmd/mxcli/skillpack/skillpack.go b/cmd/mxcli/skillpack/skillpack.go index 03df6d8c0..ccaae40d4 100644 --- a/cmd/mxcli/skillpack/skillpack.go +++ b/cmd/mxcli/skillpack/skillpack.go @@ -29,6 +29,7 @@ import ( "os" "path" "path/filepath" + "regexp" "sort" "strings" @@ -38,6 +39,22 @@ import ( // ManifestName is the per-pack manifest, read from the pack root. const ManifestName = "pack.yaml" +// LockName records the values substituted into an installed pack. +// +// It exists so `skill upgrade` re-substitutes what the install chose rather than +// re-deriving it. Re-deriving would silently change a widget's namespace when +// the project is renamed or upgraded from a different directory — and a changed +// widget id is not a build error, it is every page in the app pointing at a +// widget that no longer exists under that name. +const LockName = "pack.lock.yaml" + +// Lock is the on-disk record of an install. +type Lock struct { + Pack string `yaml:"pack"` + Version string `yaml:"version"` + Vars map[string]string `yaml:"vars"` +} + // Manifest describes a pack. Only Name is required; everything else is optional // so that a pack can be added before its install story is settled. type Manifest struct { @@ -48,6 +65,17 @@ type Manifest struct { Source string `yaml:"source"` Installs Installs `yaml:"installs"` Verify string `yaml:"verify"` + Rewrite Rewrite `yaml:"rewrite"` +} + +// Rewrite names the files whose placeholders are substituted at install time. +// +// Only the listed files are touched. That is a deliberate whitelist rather than +// a scan: a pack ships megabytes of built JavaScript and spec JSON, and a +// blind search-and-replace across all of it is how a chart spec containing the +// literal text of a token quietly becomes something else. +type Rewrite struct { + Files []string `yaml:"files"` } // Installs lists what a pack does to a project beyond copying its own files. @@ -69,6 +97,60 @@ type Pack struct { // modify the .mpr. Callers use it to decide whether to demand confirmation. func (p Pack) WritesToModel() bool { return len(p.Installs.MDL) > 0 } +// NeedsNamespace reports whether this pack carries files to substitute, and so +// cannot be installed without knowing the destination project. +func (p Pack) NeedsNamespace() bool { return len(p.Rewrite.Files) > 0 } + +// Options carries what a pack needs to know about the destination. +type Options struct { + // Vars are substituted into Rewrite.Files as {{NAME}}. + Vars map[string]string +} + +// tokenPattern matches an unsubstituted placeholder. +var tokenPattern = regexp.MustCompile(`\{\{[A-Z_]+\}\}`) + +// substitute replaces every {{TOKEN}} in content, and refuses to return a file +// that still carries one. +// +// The refusal is the point of the whole mechanism. A pluggable widget's id is +// its identity: shipping one project's namespace to another means two apps +// claiming the same widget, and the symptom is not a build error but a widget +// that resolves to somebody else's build. A missed substitution has to be +// impossible, not unlikely — so an unknown token is an error, and so is a +// declared file that turns out to carry no token at all, which means the pack +// drifted away from its manifest. +func substitute(rel string, content []byte, vars map[string]string) ([]byte, error) { + if !tokenPattern.Match(content) { + return nil, fmt.Errorf("%s is listed under rewrite.files but carries no {{TOKEN}}; "+ + "either the file changed or the manifest is stale", rel) + } + var missing []string + out := tokenPattern.ReplaceAllFunc(content, func(tok []byte) []byte { + name := string(tok[2 : len(tok)-2]) + if v, ok := vars[name]; ok { + return []byte(v) + } + missing = append(missing, name) + return tok + }) + if len(missing) > 0 { + sort.Strings(missing) + return nil, fmt.Errorf("%s: no value for %s", rel, strings.Join(uniq(missing), ", ")) + } + return out, nil +} + +func uniq(in []string) []string { + var out []string + for i, s := range in { + if i == 0 || s != in[i-1] { + out = append(out, s) + } + } + return out +} + // List returns every pack in the FS, sorted by name. A directory without a // readable manifest is an error rather than a skip: a pack that silently does // not appear is indistinguishable from one that was never vendored. @@ -130,6 +212,12 @@ func (r Result) Changed() bool { return len(r.Written) > 0 || len(r.Pruned) > 0 // // destDir is the skills directory of the target project (e.g. .claude/skills). func Install(fsys fs.FS, name, destDir string) (Result, error) { + return InstallWith(fsys, name, destDir, Options{}) +} + +// InstallWith is Install with destination-specific values substituted into the +// pack's declared rewrite files. +func InstallWith(fsys fs.FS, name, destDir string, opts Options) (Result, error) { pack, err := Load(fsys, name) if err != nil { return Result{}, err @@ -137,6 +225,24 @@ func Install(fsys fs.FS, name, destDir string) (Result, error) { res := Result{Pack: pack.Name} target := filepath.Join(destDir, pack.Name) + // The lock is written by the install, not shipped by the pack, so it must + // survive the prune that removes everything the pack no longer ships. + shippedExtra := map[string]bool{LockName: true} + + rewrites := map[string]bool{} + for _, f := range pack.Rewrite.Files { + rewrites[filepath.ToSlash(f)] = true + } + if len(rewrites) > 0 && len(opts.Vars) == 0 { + // Fall back to what a previous install recorded, so `skill upgrade` + // keeps the namespace it already has. + if prev, err := ReadLock(destDir, name); err == nil && len(prev.Vars) > 0 { + opts.Vars = prev.Vars + } else { + return res, fmt.Errorf("pack %q needs destination values (namespace) before it can be installed", name) + } + } + shipped := map[string]bool{} err = fs.WalkDir(fsys, pack.Dir, func(p string, d fs.DirEntry, err error) error { @@ -158,6 +264,13 @@ func Install(fsys fs.FS, name, destDir string) (Result, error) { if err != nil { return err } + if rewrites[rel] { + want, err = substitute(rel, want, opts.Vars) + if err != nil { + return err + } + delete(rewrites, rel) + } dst := filepath.Join(target, filepath.FromSlash(rel)) if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { return err @@ -175,6 +288,27 @@ func Install(fsys fs.FS, name, destDir string) (Result, error) { if err != nil { return res, fmt.Errorf("installing pack %q: %w", name, err) } + // A manifest naming a file the pack does not ship is a stale manifest, and + // the file it meant to rewrite would have gone out untouched. + if len(rewrites) > 0 { + var left []string + for f := range rewrites { + left = append(left, f) + } + sort.Strings(left) + return res, fmt.Errorf("pack %q: rewrite.files names %s, which the pack does not ship", + name, strings.Join(left, ", ")) + } + + for k := range shippedExtra { + shipped[k] = true + } + + if len(opts.Vars) > 0 { + if err := writeLock(target, Lock{Pack: pack.Name, Version: pack.Version, Vars: opts.Vars}); err != nil { + return res, err + } + } pruned, err := prune(target, shipped) if err != nil { @@ -306,3 +440,31 @@ func Installed(destDir string) ([]string, error) { sort.Strings(names) return names, nil } + +// writeLock records what was substituted, so a later upgrade repeats it. +func writeLock(target string, l Lock) error { + raw, err := yaml.Marshal(l) + if err != nil { + return err + } + header := []byte("# Written by `mxcli skill add`. Records the values substituted into this\n" + + "# install so `mxcli skill upgrade` repeats them rather than re-deriving —\n" + + "# a changed widget id would orphan every page that references it.\n") + if err := os.MkdirAll(target, 0o755); err != nil { + return err + } + return os.WriteFile(filepath.Join(target, LockName), append(header, raw...), 0o644) +} + +// ReadLock returns what a previous install recorded for this pack. +func ReadLock(destDir, name string) (Lock, error) { + raw, err := os.ReadFile(filepath.Join(destDir, name, LockName)) + if err != nil { + return Lock{}, err + } + var l Lock + if err := yaml.Unmarshal(raw, &l); err != nil { + return Lock{}, err + } + return l, nil +} diff --git a/cmd/mxcli/skillpacks_test.go b/cmd/mxcli/skillpacks_test.go new file mode 100644 index 000000000..3d4e20c2b --- /dev/null +++ b/cmd/mxcli/skillpacks_test.go @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "io/fs" + "regexp" + "strings" + "testing" + + "github.com/mendixlabs/mxcli/cmd/mxcli/skillpack" +) + +// TestVendoredPacksLoad checks every pack that actually ships in this binary. +// The unit tests in cmd/mxcli/skillpack prove the mechanism against fixtures; +// this proves the vendored content matches it. A pack that is broken only when +// vendored fails at somebody's install otherwise, which is the worst place to +// find out. +func TestVendoredPacksLoad(t *testing.T) { + fsys, err := packsFS() + if err != nil { + t.Fatalf("packsFS: %v", err) + } + packs, err := skillpack.List(fsys) + if err != nil { + t.Fatalf("List: %v", err) + } + if len(packs) == 0 { + t.Fatal("no packs are embedded; run `make sync-skill-packs`") + } + + for _, p := range packs { + t.Run(p.Name, func(t *testing.T) { + if p.Version == "" { + t.Error("no version") + } + if p.Description == "" { + t.Error("no description") + } + // Every file the manifest says it rewrites must exist and carry a + // token. Install enforces this too, but only when someone installs. + for _, rel := range p.Rewrite.Files { + full := p.Dir + "/" + rel + body, err := fs.ReadFile(fsys, full) + if err != nil { + t.Errorf("rewrite.files names %s, which is not shipped: %v", rel, err) + continue + } + if !regexp.MustCompile(`\{\{[A-Z_]+\}\}`).Match(body) { + t.Errorf("%s is listed under rewrite.files but carries no {{TOKEN}}", rel) + } + } + // Anything the manifest promises to install has to be there. + for _, w := range p.Installs.Widgets { + if _, err := fs.Stat(fsys, p.Dir+"/"+w); err != nil { + t.Errorf("installs.widgets names %s, which is not shipped: %v", w, err) + } + } + for _, m := range p.Installs.MDL { + if _, err := fs.Stat(fsys, p.Dir+"/"+m); err != nil { + t.Errorf("installs.mdl names %s, which is not shipped: %v", m, err) + } + } + }) + } +} + +// TestVendoredPacksCarryNoForeignNamespace is the vendoring hazard with teeth. +// +// These packs came from a real project, and a widget id is identity: if one +// ships with `ledger.widget.web` still in it, every project installing it builds +// a widget claiming to be the ledger's. That is not a build error — it is two +// apps whose widgets collide, discovered late. +// +// The check is deliberately on the widget source only. Prose may name the +// project it came from, and the specs are the ledger's own data. +func TestVendoredPacksCarryNoForeignNamespace(t *testing.T) { + fsys, err := packsFS() + if err != nil { + t.Fatalf("packsFS: %v", err) + } + // Namespaces of the projects these packs were harvested from. A pack must + // carry a placeholder instead. + foreign := []string{"ledger.widget", "ledger/widget"} + + err = fs.WalkDir(fsys, ".", func(p string, d fs.DirEntry, err error) error { + if err != nil || d.IsDir() { + return err + } + if !strings.Contains(p, "/widget/") { + return nil + } + body, err := fs.ReadFile(fsys, p) + if err != nil { + return err + } + for _, f := range foreign { + if strings.Contains(string(body), f) { + t.Errorf("%s still carries the harvested project's namespace (%q); "+ + "it must be a {{NAMESPACE}} placeholder", p, f) + } + } + return nil + }) + if err != nil { + t.Fatal(err) + } +} diff --git a/docs/11-proposals/PROPOSAL_skill_packs.md b/docs/11-proposals/PROPOSAL_skill_packs.md index 0a3c2cf3a..49b5ea0f5 100644 --- a/docs/11-proposals/PROPOSAL_skill_packs.md +++ b/docs/11-proposals/PROPOSAL_skill_packs.md @@ -1,6 +1,6 @@ --- title: Skill packs — shipping a skill that carries more than prose -status: proposed +status: partial date: 2026-08-15 related: - cmd/mxcli/skills_content.go @@ -129,6 +129,44 @@ already: generated regions are digest-fenced, and a block carrying local edits i Packs reuse it. `mxcli skill upgrade` reports what it refused and why; it never silently reverts a spec the user tuned. +### The namespace has to be right before the build + +A pluggable widget's id (`acme.widget.web.vegachart.VegaChart`) is its identity. +Two apps whose widgets share an id are two apps claiming the same widget, and the +symptom is not a build error — it is a widget resolving to somebody else's build. + +So the widget source ships with **placeholders**, not a real namespace, and +`skill add` substitutes the destination project's: + +| File | Carries | +|---|---| +| `widget/package.json` | `packagePath`, and the build's `projectPath` | +| `widget/src/package.xml` | the client-module file path | +| `widget/src/VegaChart.xml` | the widget id | + +Three properties make a missed substitution impossible rather than unlikely: + +1. **Placeholders, not a real namespace.** Leaving the harvested project's name + in place means a bug ships *their* namespace silently; an unsubstituted + `{{NAMESPACE}}` fails loudly. +2. **A whitelist, not a scan.** Only files named in `rewrite.files` are touched. + A pack ships megabytes of built JS and spec JSON, and a blind replace is how a + spec containing brace syntax quietly becomes something else. +3. **Drift in either direction is an error** — a declared file with no token + (the file changed under the manifest) and a declared file the pack does not + ship both refuse the install. + +`skill upgrade` re-substitutes what the install recorded in `pack.lock.yaml` +rather than re-deriving. Re-deriving would change the id when the project is +renamed, and a changed widget id is every page pointing at a widget that no +longer exists under that name. + +**Widgets ship as source, not as a built `.mpk`.** The built package is 3.1 MB of +bundled Vega, which has no business in a source repo or in the mxcli binary; and +the namespace has to be right *before* the build, so shipping a prebuilt package +would mean rewriting paths inside a zip and hoping, where rewriting source is the +path the ledger verified. + ### Manifest ```yaml @@ -161,13 +199,10 @@ mxcli init --with [,] # at project creation ## What this does not solve -**Project-neutrality of the ledger's packs.** Both reference `Ledger.*` entity -names, and `mendix-vega-charts` ships a widget under `ledger.widget.web.vegachart` -with re-namespacing steps written out in `references/install.md`. Vendoring them -as-is would hand every project the ledger's namespace. Either the widget is -re-published under a neutral namespace before it is vendored, or the rename is -automated as part of `skill add`. This is a prerequisite for the vega pack -specifically, not for the mechanism. +**Prose still names the project the packs came from.** The widget source is +placeholdered and guarded by a test, and the specs are the ledger's own sample +data, which is fine. But `references/*.md` still uses `Ledger.*` entity names in +its examples. That is illustrative rather than load-bearing, and left alone. **Verifying a pack in CI.** `mendix-vega-charts` ships a Node checker and seven specs; `mendix-bulk-oql-dml` ships an MDL file that `make check-skill-mdl` should @@ -181,7 +216,8 @@ pack that rots. | 1 | Embed + recursive sync + relative-path write + prune. One pack fixture, no CLI surface. Proves the mechanism carries nested non-Markdown assets through `init`. | | 2 | `pack.yaml`, version gating, `mxcli skill list/add/remove/upgrade`, digest-fenced local-edit refusal. | | 3 | Vendor `mendix-bulk-oql-dml` (no widget, so no namespace question), wire its MDL into `make check-skill-mdl`. | -| 4 | Vendor `mendix-vega-charts` once the widget namespace is settled; run `check-spec.mjs` over the shipped specs in CI. | +| 4 | Vendor `mendix-vega-charts` with install-time namespace substitution. | +| 5 | Run `check-spec.mjs` over the shipped specs in CI. | Slice 1 is worth landing on its own: it removes the silent-flattening hazard in the write path whether or not any pack ever ships. From 0bf93824179fc0532fd5d567c9a530d7f52b81dc Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Aug 2026 21:43:50 +0000 Subject: [PATCH 07/22] mxcli test: fail closed on an @expect it cannot evaluate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `mxcli test` matched one assertion shape with a regular expression — `@expect $var (=|<>) ` — and when FindStringSubmatch returned nil it recorded no assertion at all. A test with no assertions passes as long as its body does not throw, so every other shape passed unconditionally, with nothing in the output to tell it apart from a real assertion: /** * @test a self-evident falsehood * @expect 1 = 2 */ PASS a self-evident falsehood (6ms) Also vacuous: length(), find(), substring(), any comparison other than `=`/`<>`, and `!=`. In the reporting project 16 of 22 tests asserted nothing beyond "did not throw", and the suite had said 22/22 at every commit — mutation testing is what exposed it, with mutants returning obviously wrong values surviving the run. The narrow support was never the defect; the silence was. The whole annotation body now goes to a validating parser (expect.go): a strict recursive-descent pass over exprcheck.Lex — not mdl/exprcheck's own parser, which recovers and emits hints, exactly the wrong behaviour here. Anything it cannot compile becomes an ExpectErrors entry, the test is not generated at all, and the runner reports StatusError, which FailCount counts, so the run exits non-zero: ERROR an assertion nobody can evaluate @expect randomInt($result) = 1: randomInt() is not a Mendix expression function at column 1 ("randomInt") Everything Mendix's expression engine accepts now works: built-ins, every comparison operator, and/or/not, attribute paths, enumeration values. The branch-swapping workaround for `<>` is gone — the operator is rewritten to `!=` at parse time, which is the spelling Mendix accepts. A failure also reports what came back, not only what was wanted, when the assertion pins the observed value's type: a String operand is used directly, a known non-String scalar is wrapped in toString(), and nothing is emitted when neither side establishes a type. Mendix's expression engine is typed and a wrong guess fails the build rather than the test. The summary line separates Errors from Failed, because output that cannot distinguish "this assertion is false" from "this assertion was never evaluated" is how the defect stayed invisible. Measured against mxbuild 11.6.6, on eleven generated microflows covering every shape in the report: 0 errors. Two controls make that mean something — `<>` really is CE0117, so the rewrite is load-bearing, and a wrongly typed comparison really is caught (`$result = 3` on a String is CE0117). Stubbing ParseExpect back to the old regex returns every canary in expect_test.go to an empty condition. Reported as mxcli-sudoku FINDINGS #46. Repro: mdl-examples/bug-tests/expect-vacuous-assertions.mdl Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01CqQLyxnppZGfqPYbUHvSWG --- .claude/skills/fix-issue.md | 1 + .claude/skills/mendix/test-microflows.md | 50 +- cmd/mxcli/syntax/features_misc.go | 17 +- cmd/mxcli/testrunner/expect.go | 659 ++++++++++++++++++ cmd/mxcli/testrunner/expect_test.go | 272 ++++++++ cmd/mxcli/testrunner/generator.go | 84 +-- cmd/mxcli/testrunner/generator_endpoint.go | 47 +- .../testrunner/generator_endpoint_test.go | 22 +- cmd/mxcli/testrunner/generator_test.go | 4 +- cmd/mxcli/testrunner/parser.go | 105 +-- cmd/mxcli/testrunner/parser_test.go | 18 +- cmd/mxcli/testrunner/results.go | 53 +- cmd/mxcli/testrunner/runner.go | 9 +- cmd/mxcli/testrunner/runner_endpoint.go | 5 + docs-site/src/tools/running-tests.md | 44 ++ .../bug-tests/expect-vacuous-assertions.mdl | 190 +++++ 16 files changed, 1437 insertions(+), 143 deletions(-) create mode 100644 cmd/mxcli/testrunner/expect.go create mode 100644 cmd/mxcli/testrunner/expect_test.go create mode 100644 mdl-examples/bug-tests/expect-vacuous-assertions.mdl diff --git a/.claude/skills/fix-issue.md b/.claude/skills/fix-issue.md index 7ebcf0e38..d5177d481 100644 --- a/.claude/skills/fix-issue.md +++ b/.claude/skills/fix-issue.md @@ -516,3 +516,4 @@ extracting `OffsetExpression`/`LimitExpression`. | `DESCRIBE PAGE` output will not re-parse for any page carrying a **pluggable widget**: `mxcli check` on it fails with `extraneous input ':'` / `extraneous input '('` from the first widget onward. Separately, a boolean property the author set is missing from the description entirely | Two independent defects in the same path. **Emit**: explicit properties were written with a raw `%s`, so every string lost its quotes — a JSON `spec: {"a": 1}` then broke the parse at its first brace. **Read**: `extractExplicitProperties` skipped any value of `"true"`/`"false"` as a "common default", so booleans never reached the output | `mdl/executor/cmd_pages_describe_output.go` (`explicitPropValue`, `isBareLiteral`), `mdl/executor/cmd_pages_describe_pluggable.go` (`buildPropertyValueTypeMap`, `extractExplicitProperties`), `mdl/executor/cmd_pages_describe.go` (`rawExplicitProp.ValueType`) | **Fixing one half alone is worse than the bug.** Quote without emitting booleans and the description re-parses cleanly while silently dropping a property — a wrong page that validates. Both halves ship together or neither. **Quote by the DECLARED type, never the value's shape**: `ValueType.Type` sits in the widget's `Type.ObjectType.PropertyTypes`, the same array `buildPropertyTypeKeyMap` already walks for `PropertyKey` and throws away; a String property holding `"30"` or `"true"` is indistinguishable from a number once it is a string in BSON, and must still come back quoted. Where no type is declared, fall back to the value's shape and quote anything not plainly numeric or boolean — quoting is the safe direction, since an unquoted arbitrary string may not parse at all. **The round trip is the test, not the output**: describe → `check` → `exec` → describe must be byte-identical and leave `mx check` at 0 errors. Tests `mdl/executor/cmd_pages_describe_pluggable_roundtrip_test.go`; example `mdl-examples/bug-tests/pluggable-describe-roundtrip.mdl`, verified end to end on 11.12.1. Reported in mxcli-ledger FINDINGS #104 | | Authoring a pluggable widget property that lives in a **conditionally shown group** writes a widget Mendix rejects with **CE0463**, while the same widget's other properties take non-default values happily. On ProgressCircle both `showLabel: true` and `labelType: 'percentage'` do it; `showLabel: false` and General-group properties are clean. No DESCRIBE involved | Two gaps on the same axis. **Serialization**: #574 nulls the TextTemplate of a HIDDEN conditional property, but left a VISIBLE one null — and Mendix stores an empty `Forms$ClientTemplate` there. Null and empty are each invalid in the other's state, so nulling hidden ones was only half the rule. **Extraction**: the editorConfig reader did not understand a ternary's ELSE branch (`cond ? (…) : hidePropertiesIn([…])`), so the `showLabel` gate was never seen and `labelText` read as visible whenever `labelType` was `"text"` — its default | `mdl/backend/widgetobj/builder.go` (`ApplyVisibilityRules`, `bsonFieldIsNil`), `mdl/executor/editorconfig_extract.go` (`parseGuard` `:` case, `ternaryCondition`, `trailingExpr`) | **Fixing one gap alone inverts the bug rather than closing it** — filling visible templates without the missing gate made `showLabel: false` fail where `true` had, because mxcli still thought labelText was visible. Measured both ways round before and after; a fix that moves which case fails is not a fix. **Let Mendix say what the shape should be**: `mx update-widgets` on a COPY of the failing project reconciles the widget, and diffing that against mxcli's output named the single meaningful path (`Object/Properties[N]/Value/TextTemplate` null vs `Forms$ClientTemplate`) out of 969. **The good/bad control does the isolation for free**: authoring the same widget with the boolean both ways gave two documents differing in exactly ONE path, so no other candidate needed testing. **Only CONDITIONAL properties are filled** — Studio Pro's convention for an unset TextTemplate is not uniform (a DataGrid custom-content column stores null for `tooltip` and an empty template for `exportValue`, per `emptyClientTemplateRules`), so filling every unset one would trade this bug for its mirror image. **The extractor's preamble matters**: the ternary is preceded by a whole `switch`, and walking back past the `?` to the function start yields a fragment with an unbalanced `}` that parses to nothing and looks like "unsupported shape" — hence `trailingExpr`. Regression signal: the widgetdemo showcase applies with **0 CE0463** (its 4 CE1613 are a pre-existing attribute reference). Tests `mdl/backend/widgetobj/widget_visibility_test.go`, `mdl/executor/editorconfig_extract_test.go`; example `mdl-examples/bug-tests/pluggable-describe-roundtrip.mdl` now exercises the group. Reported in mxcli-ledger FINDINGS #104 follow-on | | A microflow computes a **different number than the expression says**, while `mxcli check`, `mx check` and the build are all green. An additive chain comes back from `DESCRIBE MICROFLOW` with its `+` and `-` exchanged — `$A - $B + 1` stored as `$A + $B - 1`. All-plus and all-minus chains are fine, as is `-` against `*` | `buildAdditiveExpression` read `AllPLUS()` and `AllMINUS()` as two separate token lists and emitted **every plus before every minus**, discarding source order. The precise rule is "the chain is re-sorted, all `+` ahead of all `-`" — sharper than "a `-` followed by a `+` swaps", and it predicts which cases survive | `mdl/visitor/visitor_microflow_expression.go` (`buildAdditiveExpression`) | **The fix already existed 20 lines below**: `buildMultiplicativeExpression` walks `GetChildren()` in order and builds its operator list correctly, so the additive case is that pattern copied — no new mechanism. The corruption is in the **stored model**, not in DESCRIBE: `strings` on the `.mxunit` shows the swapped text, which is why the running app computes it. A rewritten expression is perfectly valid, so no validator can catch this class — the only test that works is round-trip equality, not "does it apply cleanly". **The control cases carry the weight**: `$A - $B - 1` and `$A + $B - 1` pass both before and after, so a test built only from failing cases would have passed against code that sorted all minuses first instead. Verified by reverting the fix and confirming exactly the four swapped cases fail. Test `mdl/visitor/visitor_additive_order_test.go`; example `mdl-examples/bug-tests/additive-operator-order.mdl`. Reported in mxcli-ledger FINDINGS #105 | +| `mxcli test` reports **PASS for an assertion that must fail** — `@expect 1 = 2`, `@expect length($result) = 999`, `@expect find($result, 'Z') >= 0` with the needle absent. Nothing in the output distinguishes a real assertion from a vacuous one, so a suite certifies work as verified while asserting only that the microflow did not throw. Mutation testing is what exposes it: mutants that return an obviously wrong value survive the suite | The `@expect` annotation was matched with a regex for one shape — `@expect $var (=|<>) ` — and `FindStringSubmatch` returning nil produced **no assertion at all** rather than an error. A test with zero assertions passes if its body completes. So the narrow support was not the defect; the silence was | `cmd/mxcli/testrunner/parser.go` (`expectPattern`, `parseAnnotations`), `cmd/mxcli/testrunner/expect.go` (new — `ParseExpect`, the validating parser), `cmd/mxcli/testrunner/generator_endpoint.go` + `generator.go` (emit the condition, not a rebuilt equality), `cmd/mxcli/testrunner/results.go` (`expectErrorResult`) | Capture the **whole** annotation body and hand it to a validating parser; anything it cannot compile becomes an `ExpectErrors` entry, the test is not generated at all, and the runner reports `StatusError` (which `FailCount` counts, so the exit code is non-zero). The parser is a strict recursive-descent pass over `exprcheck.Lex` — **not** `mdl/exprcheck`'s own parser, which recovers and emits hints, exactly the wrong behaviour here. Two measurements pinned the emitted expression against mxbuild 11.6.6: `<>` really is CE0117 (so the rewrite to `!=` is load-bearing, not cosmetic) and a wrong-typed comparison really is caught (`$result = 3` → CE0117), which is what makes the 0-error run on the 11 generated shapes mean something. **Generalisable**: when a pattern-matching parser can match *less* than its input, the non-match branch is a silent-failure path — audit every `if m := re.FindStringSubmatch(...); m != nil` whose else-branch does nothing. Repro `mdl-examples/bug-tests/expect-vacuous-assertions.mdl`. mxcli-sudoku FINDINGS #46 | diff --git a/.claude/skills/mendix/test-microflows.md b/.claude/skills/mendix/test-microflows.md index 069c7eaea..b20c0d64e 100644 --- a/.claude/skills/mendix/test-microflows.md +++ b/.claude/skills/mendix/test-microflows.md @@ -152,12 +152,58 @@ The markdown format turns your tests into living documentation. | Tag | Purpose | Example | |-----|---------|---------| | `@test` | Test name (required) | `@test string concatenation` | -| `@expect` | Assert variable value | `@expect $result = 'John Doe'` | -| `@expect` | Assert entity attribute | `@expect $product/Name = 'TestProduct'` | +| `@expect` | Assert a Mendix condition | `@expect $result = 'John Doe'` | +| `@expect` | Assert an entity attribute | `@expect $product/Name = 'TestProduct'` | +| `@expect` | Assert with a built-in | `@expect length($result) = 81` | | `@verify` | OQL post-condition | `@verify select count(*) from Mod.E where Code = 'X' = 1` | | `@throws` | Expect error | `@throws 'validation failed'` | | `@cleanup` | Rollback strategy | `@cleanup rollback` (default) or `@cleanup none` | +### `@expect` — any Mendix condition, and nothing it cannot evaluate + +An `@expect` is **a Mendix expression that must evaluate to true**, not a fixed +`$var = value` shape. Anything the Mendix expression engine accepts works: + +```mdl +@expect $result = 'John Doe' -- equality +@expect $product/Name != 'Widget' -- inequality (<> also accepted) +@expect length($result) = 81 -- built-in functions +@expect find($result, '0') >= 0 -- any comparison operator +@expect substring($result, 0, 9) = substring($result, 9, 18) +@expect find($result, '0') >= 0 and $count > 3 -- and / or / not(...) +@expect $status = MyModule.Status.Open -- enumeration values +``` + +`<>` is accepted in the annotation and rewritten to `!=` on the way to the +model, because Mendix's expression engine rejects the `<>` spelling (CE0117). + +**An assertion the runner cannot compile is an ERROR, never a pass.** Unknown +functions, wrong arity, unbalanced parentheses and expressions that evaluate to +a value rather than a condition are all rejected by name: + +``` +ERROR a self-evident falsehood + @expect randomInt($result) = 1: randomInt() is not a Mendix expression + function at column 1 ("randomInt") +``` + +This is the one rule the annotation is built around. A test framework that +cannot evaluate an assertion has exactly one safe behaviour, and passing is not +it — an earlier version matched only `$var = ` and silently discarded +every other line, so `@expect 1 = 2` reported PASS. + +**A failure reports what came back**, not just what was wanted, whenever the +observed value's type is pinned down by the assertion itself: + +``` +FAIL the board is 81 squares + expected length($result) = 81, actual: 27 +``` + +The value is omitted rather than guessed when neither side of the comparison +establishes a type (`@expect $a = $b`), because Mendix's expression engine is +typed and a wrong guess would break the build instead of the test. + ### `@cleanup` — what happens to a test's data **`rollback` is the default**, so by default a test's database writes do not diff --git a/cmd/mxcli/syntax/features_misc.go b/cmd/mxcli/syntax/features_misc.go index a896affa7..2b4a2732b 100644 --- a/cmd/mxcli/syntax/features_misc.go +++ b/cmd/mxcli/syntax/features_misc.go @@ -606,8 +606,20 @@ Flags: Annotations: @test Test name (required) - @expect $var = value Assert variable equals value - @expect $obj/Attr = val Assert entity attribute + @expect A Mendix expression that must evaluate to true. + Any expression the engine accepts works: + $result = 'John Doe' + $product/Name != 'Widget' (<> is accepted too) + length($result) = 81 + find($result, '0') >= 0 + substring($r, 0, 9) = substring($r, 9, 18) + find($r, '0') >= 0 and $count > 3 + An assertion the runner cannot compile — unknown + function, wrong arity, or an expression that + yields a value rather than a condition — is an + ERROR against that test, never a pass. A failure + reports the observed value alongside the + expectation whenever the assertion pins its type. @throws 'message' Expect error @cleanup rollback|none What happens to the test's database writes. rollback (the default) wraps the test in a @@ -634,6 +646,7 @@ Cost of a run: /** * @test String concatenation * @expect $result = 'John Doe' + * @expect length($result) = 8 */ $result = CALL MICROFLOW MyModule.ConcatNames( FirstName = 'John', LastName = 'Doe' diff --git a/cmd/mxcli/testrunner/expect.go b/cmd/mxcli/testrunner/expect.go new file mode 100644 index 000000000..9b345d2d5 --- /dev/null +++ b/cmd/mxcli/testrunner/expect.go @@ -0,0 +1,659 @@ +// SPDX-License-Identifier: Apache-2.0 + +package testrunner + +import ( + "fmt" + "strings" + + "github.com/mendixlabs/mxcli/mdl/exprcheck" +) + +// Expect is one @expect assertion. +// +// It holds the annotation as written (for the failure message) and the Mendix +// boolean expression the generated microflow evaluates. The two are not the same +// string: `<>` is accepted in an annotation and rewritten to `!=`, which is the +// spelling Mendix's expression engine understands. +type Expect struct { + // Raw is the annotation text as the author wrote it. + Raw string + // Condition is the Mendix boolean expression that must hold for the test to + // pass. It is re-rendered from the parsed expression, so it is exactly the + // text that was validated. + Condition string + // Actual is a Mendix expression yielding the observed value as a String, for + // the failure message. It is empty when no such expression can be derived + // without guessing the operand's type — see actualExpr. + Actual string +} + +// ParseExpect parses one @expect annotation body. +// +// Every assertion the runner cannot evaluate is an error here, and an error +// makes the test an ERROR rather than a PASS. That is the whole point: the +// previous implementation matched `$var = ` with a regular expression +// and silently discarded every line that did not fit, so a test whose only +// assertion was `1 = 2` reported PASS. An assertion a test framework cannot +// evaluate has exactly one safe outcome, and passing is not it. +func ParseExpect(raw string) (Expect, error) { + raw = strings.TrimSpace(raw) + if raw == "" { + return Expect{}, fmt.Errorf("@expect needs an expression") + } + + toks := exprcheck.Lex(raw) + p := &expectParser{toks: toks, src: raw} + node, err := p.parse() + if err != nil { + return Expect{}, err + } + + if !isAssertionShaped(node) { + return Expect{}, fmt.Errorf( + "@expect %s is not a condition: it evaluates to a value, not to true or false "+ + "(did you mean to compare it with = or !=?)", raw) + } + + return Expect{ + Raw: raw, + Condition: node.render(), + Actual: actualExpr(node), + }, nil +} + +// isAssertionShaped reports whether the expression can be a pass/fail condition. +// +// A comparison, a logical operator, a Boolean-returning call and a bare variable +// all qualify; a bare literal or an arithmetic expression does not. `1 = 2` is a +// comparison, so it qualifies and — correctly — fails. +func isAssertionShaped(n expectNode) bool { + switch e := n.(type) { + case *expectBinary: + switch e.Op { + case "=", "!=", "<", "<=", ">", ">=", "and", "or": + return true + } + return false + case *expectCall: + if e.Name == "not" { + return true + } + kind, known := exprcheck.FuncReturnKind(e.Name) + return !known || kind == exprcheck.KindBoolean + case *expectParen: + return isAssertionShaped(e.Inner) + case *expectVar: + // A Boolean variable or attribute is a valid condition on its own. + return true + case *expectLiteral: + return e.Kind == exprcheck.KindBoolean + case *expectIfThenElse: + // Mendix's if-then-else binds loosest, so `if c then a else b = 1` is + // `if c then a else (b = 1)`; the whole thing is a condition only when + // both branches are. + return isAssertionShaped(e.Then) && isAssertionShaped(e.Else) + } + return false +} + +// actualExpr returns a Mendix expression that renders the observed value as a +// String, or "" when one cannot be derived safely. +// +// "Safely" is doing real work here. Mendix's expression engine is typed: `+` +// only concatenates Strings, and toString() rejects a String operand. So the +// observed value is only reported when its type is pinned down, and it is pinned +// down in two ways: the operand's own inferred kind, and — when that is unknown — +// the kind of the other side of the comparison, which must match for the +// comparison itself to have compiled at all. A String operand is used directly; +// a known non-String scalar is wrapped in toString(). Anything else reports no +// actual value rather than emitting an expression that may not compile. +// +// Measured against mxbuild 11.6.6, Mendix is in fact more permissive than this +// rule assumes — `toString()` accepts a String and `+` accepts an Integer, both +// at 0 errors. The rule stays conservative anyway: `toString()` on an *object* +// is not covered by that measurement, and the cost of guessing wrong is a +// microflow that will not compile, which fails the test for a reason that has +// nothing to do with what it was asserting. The same run confirmed the check is +// not blind to this class — `$result = 3` on a String is CE0117. +func actualExpr(n expectNode) string { + cmp, ok := n.(*expectBinary) + if !ok { + return "" + } + switch cmp.Op { + case "=", "!=", "<", "<=", ">", ">=": + default: + return "" + } + + left, right := cmp.Left, cmp.Right + _, leftLit := left.(*expectLiteral) + _, rightLit := right.(*expectLiteral) + if leftLit && rightLit { + // `1 = 2` asserts something about nothing the test computed, so there is + // no observed value to report. + return "" + } + // The interesting side is the one that is not a literal — that is the value + // the test observed. + if leftLit { + left, right = right, left + } + lk, rk := left.kind(), right.kind() + + switch { + case lk == exprcheck.KindString: + return left.render() + case isStringifiableScalar(lk): + return "toString(" + left.render() + ")" + case lk == exprcheck.KindUnknown && rk == exprcheck.KindString: + // The comparison only compiles if both sides are Strings. + return left.render() + case lk == exprcheck.KindUnknown && isStringifiableScalar(rk): + return "toString(" + left.render() + ")" + } + return "" +} + +// isStringifiableScalar reports whether toString() is defined for the kind and +// the kind is not already a String. +func isStringifiableScalar(k exprcheck.TypeKind) bool { + switch k { + case exprcheck.KindBoolean, exprcheck.KindInteger, exprcheck.KindLong, + exprcheck.KindDecimal, exprcheck.KindDateTime: + return true + } + return false +} + +// ----------------------------------------------------------------------------- +// Expression tree +// +// This is a validating parser, not the recovering one in mdl/exprcheck: that one +// is built to keep going and emit hints, which is the right behaviour for a +// linter and the wrong behaviour here. Every construct it does not recognise +// must stop the parse. +// ----------------------------------------------------------------------------- + +type expectNode interface { + render() string + kind() exprcheck.TypeKind +} + +type expectLiteral struct { + Text string + Kind exprcheck.TypeKind +} + +func (e *expectLiteral) render() string { return e.Text } +func (e *expectLiteral) kind() exprcheck.TypeKind { return e.Kind } + +// expectVar is a variable, an attribute path (`$obj/Attr`), a qualified name +// (`Module.Enum.Value`) or a `[%Token%]`. +type expectVar struct{ Text string } + +func (e *expectVar) render() string { return e.Text } +func (e *expectVar) kind() exprcheck.TypeKind { return exprcheck.KindUnknown } + +type expectCall struct { + Name string + Args []expectNode +} + +func (e *expectCall) render() string { + parts := make([]string, len(e.Args)) + for i, a := range e.Args { + parts[i] = a.render() + } + return e.Name + "(" + strings.Join(parts, ", ") + ")" +} + +func (e *expectCall) kind() exprcheck.TypeKind { + if k, ok := exprcheck.FuncReturnKind(e.Name); ok { + return k + } + return exprcheck.KindUnknown +} + +type expectParen struct{ Inner expectNode } + +func (e *expectParen) render() string { return "(" + e.Inner.render() + ")" } +func (e *expectParen) kind() exprcheck.TypeKind { return e.Inner.kind() } + +type expectUnary struct { + Op string + Operand expectNode +} + +func (e *expectUnary) render() string { return e.Op + e.Operand.render() } +func (e *expectUnary) kind() exprcheck.TypeKind { return e.Operand.kind() } + +type expectBinary struct { + Op string + Left, Right expectNode +} + +func (e *expectBinary) render() string { + return e.Left.render() + " " + e.Op + " " + e.Right.render() +} + +func (e *expectBinary) kind() exprcheck.TypeKind { + switch e.Op { + case "=", "!=", "<", "<=", ">", ">=", "and", "or": + return exprcheck.KindBoolean + case "+": + // Mendix overloads + for concatenation; either String operand makes the + // result a String. + if e.Left.kind() == exprcheck.KindString || e.Right.kind() == exprcheck.KindString { + return exprcheck.KindString + } + } + if e.Left.kind() == e.Right.kind() { + return e.Left.kind() + } + return exprcheck.KindUnknown +} + +type expectIfThenElse struct{ Cond, Then, Else expectNode } + +func (e *expectIfThenElse) render() string { + return "if " + e.Cond.render() + " then " + e.Then.render() + " else " + e.Else.render() +} + +func (e *expectIfThenElse) kind() exprcheck.TypeKind { + if e.Then.kind() == e.Else.kind() { + return e.Then.kind() + } + return exprcheck.KindUnknown +} + +// ----------------------------------------------------------------------------- +// Parser +// ----------------------------------------------------------------------------- + +type expectParser struct { + toks []exprcheck.Token + pos int + src string +} + +func (p *expectParser) peek() exprcheck.Token { return p.toks[p.pos] } + +func (p *expectParser) next() exprcheck.Token { + t := p.toks[p.pos] + if p.pos < len(p.toks)-1 { + p.pos++ + } + return t +} + +// atKeyword reports whether the current token is the given case-insensitive +// keyword. Mendix expression keywords are lower-case; the annotation is allowed +// to use any case and is normalised on render. +func (p *expectParser) atKeyword(kw string) bool { + t := p.peek() + return t.Kind == exprcheck.TokIdent && strings.EqualFold(t.Text, kw) +} + +func (p *expectParser) errorAt(t exprcheck.Token, format string, args ...any) error { + what := t.Text + if t.Kind == exprcheck.TokEOF { + what = "end of expression" + } else { + what = fmt.Sprintf("%q", what) + } + return fmt.Errorf("@expect %s: %s at column %d (%s)", + p.src, fmt.Sprintf(format, args...), t.Pos.Column, what) +} + +func (p *expectParser) parse() (expectNode, error) { + // A lexer error token anywhere means a character the Mendix expression + // grammar has no place for, so reject before parsing. + for _, t := range p.toks { + if t.Kind == exprcheck.TokError { + return nil, p.errorAt(t, "unexpected character") + } + } + n, err := p.parseOr() + if err != nil { + return nil, err + } + if t := p.peek(); t.Kind != exprcheck.TokEOF { + return nil, p.errorAt(t, "unexpected trailing input") + } + return n, nil +} + +func (p *expectParser) parseOr() (expectNode, error) { + left, err := p.parseAnd() + if err != nil { + return nil, err + } + for p.atKeyword("or") { + p.next() + right, err := p.parseAnd() + if err != nil { + return nil, err + } + left = &expectBinary{Op: "or", Left: left, Right: right} + } + return left, nil +} + +func (p *expectParser) parseAnd() (expectNode, error) { + left, err := p.parseComparison() + if err != nil { + return nil, err + } + for p.atKeyword("and") { + p.next() + right, err := p.parseComparison() + if err != nil { + return nil, err + } + left = &expectBinary{Op: "and", Left: left, Right: right} + } + return left, nil +} + +// comparisonOps maps a lexed comparison token to the Mendix spelling. `<>` and +// `!=` both lex to TokNeq; Mendix only accepts `!=`, which is why the operator is +// taken from this table rather than from the token's own text. +var comparisonOps = map[exprcheck.TokKind]string{ + exprcheck.TokEq: "=", + exprcheck.TokNeq: "!=", + exprcheck.TokLt: "<", + exprcheck.TokLe: "<=", + exprcheck.TokGt: ">", + exprcheck.TokGe: ">=", +} + +func (p *expectParser) parseComparison() (expectNode, error) { + left, err := p.parseAdditive() + if err != nil { + return nil, err + } + op, ok := comparisonOps[p.peek().Kind] + if !ok { + return left, nil + } + p.next() + right, err := p.parseAdditive() + if err != nil { + return nil, err + } + return &expectBinary{Op: op, Left: left, Right: right}, nil +} + +func (p *expectParser) parseAdditive() (expectNode, error) { + left, err := p.parseMultiplicative() + if err != nil { + return nil, err + } + for { + var op string + switch p.peek().Kind { + case exprcheck.TokPlus: + op = "+" + case exprcheck.TokMinus: + op = "-" + default: + return left, nil + } + p.next() + right, err := p.parseMultiplicative() + if err != nil { + return nil, err + } + left = &expectBinary{Op: op, Left: left, Right: right} + } +} + +func (p *expectParser) parseMultiplicative() (expectNode, error) { + left, err := p.parseUnary() + if err != nil { + return nil, err + } + for { + var op string + switch { + case p.peek().Kind == exprcheck.TokStar: + op = "*" + case p.atKeyword("div"): + op = "div" + case p.atKeyword("mod"): + op = "mod" + default: + return left, nil + } + p.next() + right, err := p.parseUnary() + if err != nil { + return nil, err + } + left = &expectBinary{Op: op, Left: left, Right: right} + } +} + +func (p *expectParser) parseUnary() (expectNode, error) { + if p.peek().Kind == exprcheck.TokMinus { + p.next() + operand, err := p.parsePrimary() + if err != nil { + return nil, err + } + return &expectUnary{Op: "-", Operand: operand}, nil + } + return p.parsePrimary() +} + +func (p *expectParser) parsePrimary() (expectNode, error) { + t := p.peek() + switch t.Kind { + case exprcheck.TokString: + p.next() + return &expectLiteral{Text: t.Text, Kind: exprcheck.KindString}, nil + + case exprcheck.TokNumber: + p.next() + kind := exprcheck.KindInteger + if strings.Contains(t.Text, ".") { + kind = exprcheck.KindDecimal + } + return &expectLiteral{Text: t.Text, Kind: kind}, nil + + case exprcheck.TokToken: + p.next() + return &expectVar{Text: t.Text}, nil + + case exprcheck.TokDollarIdent: + return p.parseVariablePath() + + case exprcheck.TokAt: + // @Module.Constant + p.next() + name, err := p.parseDottedName() + if err != nil { + return nil, err + } + return &expectVar{Text: "@" + name}, nil + + case exprcheck.TokLParen: + p.next() + inner, err := p.parseOr() + if err != nil { + return nil, err + } + if p.peek().Kind != exprcheck.TokRParen { + return nil, p.errorAt(p.peek(), "expected a closing parenthesis") + } + p.next() + return &expectParen{Inner: inner}, nil + + case exprcheck.TokIdent: + return p.parseIdentPrimary() + } + return nil, p.errorAt(t, "expected a value") +} + +// parseVariablePath parses `$var`, `$var/Attr/Sub` and `$var.Attr`. +func (p *expectParser) parseVariablePath() (expectNode, error) { + t := p.next() + if t.Text == "$" { + return nil, p.errorAt(t, "expected a variable name after $") + } + var b strings.Builder + b.WriteString(t.Text) + for { + sep := "" + switch p.peek().Kind { + case exprcheck.TokSlash: + sep = "/" + case exprcheck.TokDot: + sep = "." + default: + return &expectVar{Text: b.String()}, nil + } + p.next() + name := p.peek() + if name.Kind != exprcheck.TokIdent { + return nil, p.errorAt(name, "expected a member name after %q", sep) + } + p.next() + b.WriteString(sep) + b.WriteString(name.Text) + } +} + +// parseDottedName parses `Module.Name` and `Module.Enum.Value`. +func (p *expectParser) parseDottedName() (string, error) { + first := p.peek() + if first.Kind != exprcheck.TokIdent { + return "", p.errorAt(first, "expected a name") + } + p.next() + var b strings.Builder + b.WriteString(first.Text) + for p.peek().Kind == exprcheck.TokDot { + p.next() + part := p.peek() + if part.Kind != exprcheck.TokIdent { + return "", p.errorAt(part, "expected a name after '.'") + } + p.next() + b.WriteString(".") + b.WriteString(part.Text) + } + return b.String(), nil +} + +// parseIdentPrimary handles the four things an identifier can start: a keyword +// literal, `if ... then ... else ...`, a function call, and a qualified name. +func (p *expectParser) parseIdentPrimary() (expectNode, error) { + t := p.peek() + switch strings.ToLower(t.Text) { + case "true", "false": + p.next() + return &expectLiteral{Text: strings.ToLower(t.Text), Kind: exprcheck.KindBoolean}, nil + case "empty": + // `empty` is Mendix's null literal, but `empty(...)` is not a function — + // so only the bare form is accepted. + p.next() + if p.peek().Kind == exprcheck.TokLParen { + return nil, p.errorAt(p.peek(), "empty is a value, not a function") + } + return &expectLiteral{Text: "empty", Kind: exprcheck.KindEmpty}, nil + case "if": + return p.parseIfThenElse() + } + + // A call: the name must be a Mendix built-in. A bare name(...) in a Mendix + // expression is always a built-in — entity and enumeration references are + // qualified names, not calls — so an unknown name is an error, not a + // user-defined function. + if p.toks[p.pos+1].Kind == exprcheck.TokLParen { + return p.parseCall() + } + + name, err := p.parseDottedName() + if err != nil { + return nil, err + } + if !strings.Contains(name, ".") { + return nil, p.errorAt(t, + "%q is not a variable, a function or a qualified name", name) + } + return &expectVar{Text: name}, nil +} + +func (p *expectParser) parseCall() (expectNode, error) { + nameTok := p.next() + name := nameTok.Text + sig, known := exprcheck.PublicFuncTable()[name] + if !known { + return nil, p.errorAt(nameTok, "%s() is not a Mendix expression function", name) + } + p.next() // consume '(' + + var args []expectNode + if p.peek().Kind != exprcheck.TokRParen { + for { + arg, err := p.parseOr() + if err != nil { + return nil, err + } + args = append(args, arg) + if p.peek().Kind != exprcheck.TokComma { + break + } + p.next() + } + } + if p.peek().Kind != exprcheck.TokRParen { + return nil, p.errorAt(p.peek(), "expected a closing parenthesis for %s()", name) + } + p.next() + + minArgs := sig.MinArgs + if minArgs == 0 { + minArgs = len(sig.Args) + } + if len(args) < minArgs || len(args) > len(sig.Args) { + return nil, p.errorAt(nameTok, "%s() takes %s, got %d", + name, arityText(minArgs, len(sig.Args)), len(args)) + } + return &expectCall{Name: name, Args: args}, nil +} + +func arityText(min, max int) string { + if min == max { + return fmt.Sprintf("%d argument(s)", min) + } + return fmt.Sprintf("%d to %d arguments", min, max) +} + +func (p *expectParser) parseIfThenElse() (expectNode, error) { + p.next() // if + cond, err := p.parseOr() + if err != nil { + return nil, err + } + if !p.atKeyword("then") { + return nil, p.errorAt(p.peek(), "expected 'then'") + } + p.next() + thenExpr, err := p.parseOr() + if err != nil { + return nil, err + } + if !p.atKeyword("else") { + return nil, p.errorAt(p.peek(), "expected 'else'") + } + p.next() + elseExpr, err := p.parseOr() + if err != nil { + return nil, err + } + return &expectIfThenElse{Cond: cond, Then: thenExpr, Else: elseExpr}, nil +} diff --git a/cmd/mxcli/testrunner/expect_test.go b/cmd/mxcli/testrunner/expect_test.go new file mode 100644 index 000000000..0601727bd --- /dev/null +++ b/cmd/mxcli/testrunner/expect_test.go @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: Apache-2.0 + +package testrunner + +import ( + "strings" + "testing" +) + +// expectOf compiles an @expect body for use in a table literal. It panics on a +// bad expression, which in a test is the same as failing it. +func expectOf(raw string) Expect { + exp, err := ParseExpect(raw) + if err != nil { + panic(err) + } + return exp +} + +// TestExpectCanariesAreEvaluated is the regression test for the silent-pass +// defect: mxcli test evaluated only `$var = ` and let every other +// assertion shape through unconditionally, with no warning and nothing in the +// output distinguishing it from a real assertion. Each row is an assertion from +// the field report that must fail; each must now compile into a condition that +// can produce a failure, and none may be dropped. +func TestExpectCanariesAreEvaluated(t *testing.T) { + canaries := []struct { + raw string + cond string + }{ + {"1 = 2", "1 = 2"}, + {"length($result) = 999", "length($result) = 999"}, + {"find($result, 'Z') >= 0", "find($result, 'Z') >= 0"}, + {"find($result, '5') < 0", "find($result, '5') < 0"}, + {"substring($result, 0, 1) = 'Z'", "substring($result, 0, 1) = 'Z'"}, + {"substring($result, 0, 1) = substring($result, 1, 1)", + "substring($result, 0, 1) = substring($result, 1, 1)"}, + {"$result != 'WRONG'", "$result != 'WRONG'"}, + {"find($result, '0') >= 0 and find($result, '0') < 0", + "find($result, '0') >= 0 and find($result, '0') < 0"}, + } + for _, c := range canaries { + exp, err := ParseExpect(c.raw) + if err != nil { + t.Errorf("ParseExpect(%q): %v", c.raw, err) + continue + } + if exp.Condition != c.cond { + t.Errorf("ParseExpect(%q).Condition = %q, want %q", c.raw, exp.Condition, c.cond) + } + } +} + +// TestExpectCanariesReachTheGeneratedMicroflow closes the loop on the canaries: +// compiling them is not enough, the condition has to end up in the microflow the +// runner actually invokes. +func TestExpectCanariesReachTheGeneratedMicroflow(t *testing.T) { + suite := &TestSuite{Name: "canary", Tests: []TestCase{{ + ID: "test_1", + Name: "a self-evident falsehood", + MDL: "$result = CALL MICROFLOW MyModule.Anything();", + Expects: []Expect{expectOf("1 = 2")}, + }}} + mdl := GenerateTestFlows(suite) + if !strings.Contains(mdl, "IF 1 = 2 THEN") { + t.Errorf("the assertion never reached the microflow:\n%s", mdl) + } +} + +// TestParseExpectRejectsWhatItCannotEvaluate is the fail-closed half. Each of +// these must be an error — never an assertion, and never silence. +func TestParseExpectRejectsWhatItCannotEvaluate(t *testing.T) { + bad := []struct { + raw string + want string + }{ + {"", "needs an expression"}, + {"'abc'", "not a condition"}, // a value, not an assertion + {"$a + $b", "not a condition"}, // arithmetic, not an assertion + {"length($result)", "not a condition"}, // Integer-valued, not an assertion + {"$result =", "expected a value"}, // truncated + {"$result = 'x' extra", "unexpected trailing input"}, // garbage after + {"randomInt($result) = 1", "not a Mendix expression function"}, + {"length($result, 2) = 1", "takes 1 argument(s), got 2"}, + {"substring($result) = 'x'", "takes 2 to 3 arguments, got 1"}, + {"$result = #", "unexpected character"}, + {"length($result = 1", "expected a closing parenthesis"}, + {"$result/ = 'x'", "expected a member name"}, + } + for _, c := range bad { + exp, err := ParseExpect(c.raw) + if err == nil { + t.Errorf("ParseExpect(%q) accepted it as %q; want an error", c.raw, exp.Condition) + continue + } + if !strings.Contains(err.Error(), c.want) { + t.Errorf("ParseExpect(%q) error = %q, want it to mention %q", c.raw, err, c.want) + } + } +} + +// TestParseExpectAcceptsRealAssertions guards against over-tightening: the +// shapes below are ordinary Mendix expressions and must keep working. +func TestParseExpectAcceptsRealAssertions(t *testing.T) { + good := []string{ + "$result = 'John Doe'", + "$product/Name = 'Widget'", + "$order/Customer/Name != empty", + "$count = 3", + "$done = true", + "not($done)", + "contains($result, 'abc')", + "trim($result) = 'x'", + "length($result) = 81", + "toUpperCase($name) = 'ABC'", + "$total = $price * 2 + 1", + "$status = MyModule.Status.Open", + "($a = 1 or $b = 2) and $c = 3", + "$user = [%CurrentUser%]", + "$done", // a Boolean variable is a condition on its own + "if $flag then $a else $b = 1", + } + for _, raw := range good { + if _, err := ParseExpect(raw); err != nil { + t.Errorf("ParseExpect(%q): %v", raw, err) + } + } +} + +// TestParseExpectRewritesNotEquals pins the one rewrite the parser performs. +// Mendix's expression engine accepts `!=` and rejects `<>`; the annotation +// accepts both because MDL's own lexer does. +func TestParseExpectRewritesNotEquals(t *testing.T) { + for _, raw := range []string{"$r <> 'John'", "$r != 'John'"} { + exp, err := ParseExpect(raw) + if err != nil { + t.Fatalf("ParseExpect(%q): %v", raw, err) + } + if exp.Condition != "$r != 'John'" { + t.Errorf("ParseExpect(%q).Condition = %q, want %q", raw, exp.Condition, "$r != 'John'") + } + } +} + +// TestExpectActualValueIsTypeSafe pins when the observed value is reported. +// +// Mendix's expression engine is typed: `+` only concatenates Strings and +// toString() rejects a String. The rule is that the operand is used directly only +// when it is known to be a String, wrapped in toString() only when it is known +// not to be, and omitted when neither is established — reporting nothing beats +// emitting an expression the build rejects. +func TestExpectActualValueIsTypeSafe(t *testing.T) { + cases := []struct { + raw string + actual string + }{ + // Comparing against a string literal proves the other side is a String. + {"$result = 'John'", "$result"}, + {"'John' = $result", "$result"}, + {"$product/Name != 'Widget'", "$product/Name"}, + // A String-returning built-in needs no wrapping. + {"substring($result, 0, 1) = 'Z'", "substring($result, 0, 1)"}, + // An Integer-returning built-in does. + {"length($result) = 999", "toString(length($result))"}, + {"find($result, 'Z') >= 0", "toString(find($result, 'Z'))"}, + // The literal pins the unknown side's type, so toString() is safe. + {"$count = 3", "toString($count)"}, + {"$done = true", "toString($done)"}, + // Neither side pins anything, or the assertion is not a comparison. + {"1 = 2", ""}, // nothing was observed + {"$a = $b", ""}, + {"$result != empty", ""}, + {"find($result, '0') >= 0 and find($result, '1') >= 0", ""}, + {"not($done)", ""}, + } + for _, c := range cases { + exp, err := ParseExpect(c.raw) + if err != nil { + t.Fatalf("ParseExpect(%q): %v", c.raw, err) + } + if exp.Actual != c.actual { + t.Errorf("ParseExpect(%q).Actual = %q, want %q", c.raw, exp.Actual, c.actual) + } + } +} + +// TestParseAnnotationsRecordsExpectErrors pins that a bad @expect survives as an +// error on the test rather than vanishing. +func TestParseAnnotationsRecordsExpectErrors(t *testing.T) { + doc := `/** + * @test broken + * @expect randomInt($result) = 1 + * @expect $result = 'ok' + */` + a := parseAnnotations(doc) + if len(a.Expects) != 1 { + t.Errorf("Expects: got %d, want 1", len(a.Expects)) + } + if len(a.ExpectErrors) != 1 { + t.Fatalf("ExpectErrors: got %d, want 1", len(a.ExpectErrors)) + } + if !strings.Contains(a.ExpectErrors[0], "randomInt") { + t.Errorf("ExpectErrors[0] = %q, want it to name the function", a.ExpectErrors[0]) + } +} + +// TestUncompilableExpectIsAnErrorNotAPass is the end-to-end statement of the +// rule: such a test is never generated, and the suite reports ERROR — which +// FailCount counts, so the run's exit code is non-zero. +func TestUncompilableExpectIsAnErrorNotAPass(t *testing.T) { + tc := TestCase{ + ID: "test_1", + Name: "broken", + MDL: "$result = CALL MICROFLOW M.Anything();", + ExpectErrors: []string{"@expect randomInt($result) = 1: randomInt() is not a Mendix expression function"}, + } + suite := &TestSuite{Name: "s", Tests: []TestCase{tc}} + + if mdl := GenerateTestFlows(suite); strings.Contains(mdl, testFlowName(tc)) { + t.Errorf("a test with an uncompilable @expect was generated:\n%s", mdl) + } + if mdl := GenerateTestRunner(suite); strings.Contains(mdl, "MXTEST:RUN:test_1") { + t.Errorf("a test with an uncompilable @expect was generated into the runner:\n%s", mdl) + } + + res, bad := expectErrorResult(tc) + if !bad { + t.Fatal("expectErrorResult did not flag the test") + } + if res.Status != StatusError { + t.Errorf("status = %v, want ERROR", res.Status) + } + sr := &SuiteResult{Tests: []TestResult{res}} + if sr.PassCount() != 0 || sr.FailCount() != 1 || sr.AllPassed() { + t.Errorf("an uncompilable @expect reported as passing: pass=%d fail=%d allPassed=%v", + sr.PassCount(), sr.FailCount(), sr.AllPassed()) + } +} + +// TestFailureMessageReportsTheActualValue pins the second half of the fix: a +// failing test that only echoes the expectation tells you nothing about what +// came back. +func TestFailureMessageReportsTheActualValue(t *testing.T) { + var b strings.Builder + writeExpectCheck(&b, expectOf("$result = 'John'")) + got := b.String() + if !strings.Contains(got, "', actual: ' + $result") { + t.Errorf("the failure message does not carry the actual value:\n%s", got) + } + if !strings.Contains(got, "expected $result = ''John''") { + t.Errorf("the failure message does not echo the assertion:\n%s", got) + } +} + +// TestSummaryDistinguishesErrorsFromFailures pins that the summary line does not +// fold a never-evaluated assertion into the failure count. The output not +// distinguishing the two is what let 16 vacuous tests sit inside a green 22/22. +func TestSummaryDistinguishesErrorsFromFailures(t *testing.T) { + sr := &SuiteResult{Name: "s", Tests: []TestResult{ + {ID: "1", Name: "ok", Status: StatusPass}, + {ID: "2", Name: "wrong", Status: StatusFail}, + {ID: "3", Name: "unevaluated", Status: StatusError}, + }} + if sr.ErrorCount() != 1 { + t.Errorf("ErrorCount = %d, want 1", sr.ErrorCount()) + } + var b strings.Builder + PrintResults(&b, sr, false) + if !strings.Contains(b.String(), "Total: 3 Passed: 1 Failed: 1 Errors: 1 Skipped: 0") { + t.Errorf("summary does not separate errors from failures:\n%s", b.String()) + } +} diff --git a/cmd/mxcli/testrunner/generator.go b/cmd/mxcli/testrunner/generator.go index 9171680b3..29f5f6543 100644 --- a/cmd/mxcli/testrunner/generator.go +++ b/cmd/mxcli/testrunner/generator.go @@ -29,6 +29,12 @@ func GenerateTestRunner(suite *TestSuite) string { b.WriteString("\n") for i, tc := range suite.Tests { + // A test whose @expect did not compile gets no block. The runner reports + // it as an ERROR from the parse message; running it would report a pass + // for an assertion that was never made. + if len(tc.ExpectErrors) > 0 { + continue + } writeTestBlock(&b, tc, i) b.WriteString("\n") } @@ -111,42 +117,31 @@ func writeThrowsTestBlock(b *strings.Builder, tc TestCase, suffix string) { } // writeExpectAssertion generates an IF/ELSE check for a single @expect assertion. -// Uses compound condition with AND to guard against checking after exception. -// Only uses = operator (not <>) since <> causes Mendix expression errors. +// +// The condition is the author's own expression, guarded by $TestFailed so an +// assertion is not evaluated after the body already threw. `<>` never reaches +// the model: ParseExpect rewrites it to `!=`, which is the spelling Mendix +// accepts — the branch-swapping this function used to do was a workaround for +// emitting `<>` verbatim. +// +// Unlike the endpoint generator this path reports failures through the log +// protocol, whose message is a single log line, so the observed value is +// concatenated into that line rather than into a returned verdict. func writeExpectAssertion(b *strings.Builder, testID string, exp Expect) { - varRef := exp.Variable - value := exp.Value + passCondition := fmt.Sprintf("$TestFailed = false and (%s)", exp.Condition) + failMsg := "MXTEST:FAIL:" + testID + ":Expected " + exp.Raw - var passCondition string - if exp.Operator == "=" { - passCondition = fmt.Sprintf("$TestFailed = false and %s = %s", varRef, value) - } else { - // For != assertions, invert: pass when values differ - passCondition = fmt.Sprintf("$TestFailed = false and %s = %s", varRef, value) - // Actually this needs to FAIL when equal — swap PASS/FAIL below - } - - if exp.Operator == "=" { - b.WriteString(fmt.Sprintf(" IF %s THEN\n", passCondition)) - b.WriteString(fmt.Sprintf(" LOG INFO NODE 'MXTEST' 'MXTEST:PASS:%s';\n", escapeMDLString(testID))) - b.WriteString(" ELSE\n") - failMsg := fmt.Sprintf("Expected %s %s %s", varRef, exp.Operator, value) - b.WriteString(fmt.Sprintf(" LOG ERROR NODE 'MXTEST' 'MXTEST:FAIL:%s:%s';\n", - escapeMDLString(testID), escapeMDLString(failMsg))) - b.WriteString(" SET $AllPassed = false;\n") - b.WriteString(" END IF;\n") + b.WriteString(fmt.Sprintf(" IF %s THEN\n", passCondition)) + b.WriteString(fmt.Sprintf(" LOG INFO NODE 'MXTEST' 'MXTEST:PASS:%s';\n", escapeMDLString(testID))) + b.WriteString(" ELSE\n") + if exp.Actual == "" { + b.WriteString(fmt.Sprintf(" LOG ERROR NODE 'MXTEST' '%s';\n", escapeMDLString(failMsg))) } else { - // != operator: pass when NOT equal, fail when equal - condition := fmt.Sprintf("$TestFailed = false and %s = %s", varRef, value) - b.WriteString(fmt.Sprintf(" IF %s THEN\n", condition)) - failMsg := fmt.Sprintf("Expected %s %s %s", varRef, exp.Operator, value) - b.WriteString(fmt.Sprintf(" LOG ERROR NODE 'MXTEST' 'MXTEST:FAIL:%s:%s';\n", - escapeMDLString(testID), escapeMDLString(failMsg))) - b.WriteString(" SET $AllPassed = false;\n") - b.WriteString(" ELSE\n") - b.WriteString(fmt.Sprintf(" LOG INFO NODE 'MXTEST' 'MXTEST:PASS:%s';\n", escapeMDLString(testID))) - b.WriteString(" END IF;\n") + b.WriteString(fmt.Sprintf(" LOG ERROR NODE 'MXTEST' '%s' + %s;\n", + escapeMDLString(failMsg+", actual: "), exp.Actual)) } + b.WriteString(" SET $AllPassed = false;\n") + b.WriteString(" END IF;\n") } // varPattern matches $VariableName in MDL ($ followed by word characters). @@ -187,18 +182,23 @@ func renameVariables(mdl string, names map[string]bool, suffix string) string { } // renameExpect applies variable renaming to an Expect assertion. +// +// The monolithic runner compiles every test into one microflow, so `$result` in +// test 1 and `$result` in test 2 have to be told apart. Renaming runs over the +// rendered condition and the actual-value expression, which is why both are kept +// as text rather than as a tree. func renameExpect(exp Expect, names map[string]bool, suffix string) Expect { + rename := func(src string) string { + return varPattern.ReplaceAllStringFunc(src, func(match string) string { + if names[match[1:]] { + return match + suffix + } + return match + }) + } renamed := exp - - // Rename the variable reference (e.g., "$result" -> "$result_1" or "$product/Name" -> "$product_1/Name") - renamed.Variable = varPattern.ReplaceAllStringFunc(exp.Variable, func(match string) string { - name := match[1:] - if names[name] { - return "$" + name + suffix - } - return match - }) - + renamed.Condition = rename(exp.Condition) + renamed.Actual = rename(exp.Actual) return renamed } diff --git a/cmd/mxcli/testrunner/generator_endpoint.go b/cmd/mxcli/testrunner/generator_endpoint.go index 92e649ca3..3ea8e37c1 100644 --- a/cmd/mxcli/testrunner/generator_endpoint.go +++ b/cmd/mxcli/testrunner/generator_endpoint.go @@ -34,6 +34,12 @@ func GenerateTestFlows(suite *TestSuite) string { var b strings.Builder b.WriteString("CREATE MODULE " + mxTestModule + ";\n\n") for _, tc := range suite.Tests { + // A test with an uncompilable @expect gets no microflow. The runner + // reports it as an ERROR from the parse message, which is more useful + // than a microflow that runs and cannot assert anything. + if len(tc.ExpectErrors) > 0 { + continue + } writeTestFlow(&b, tc) b.WriteString("\n") } @@ -87,31 +93,36 @@ func writeThrowsFlowBody(b *strings.Builder, tc TestCase) { // writeExpectCheck writes one @expect assertion. // -// Only the pass condition is expressed with `=`; a `<>` expectation is compiled -// as the same equality with the branches swapped. That is deliberate and -// inherited from the monolithic generator: `<>` in a generated Mendix expression -// produced expression errors, so the operator never reaches the model. +// The assertion is emitted as the expression the author wrote, so whatever +// Mendix can evaluate is evaluated. `<>` never reaches the model — ParseExpect +// rewrites it to `!=`, the spelling Mendix's expression engine accepts — which +// is what the old branch-swapping workaround was for. func writeExpectCheck(b *strings.Builder, exp Expect) { - equal := fmt.Sprintf("%s = %s", exp.Variable, exp.Value) - failMsg := escapeMDLString(fmt.Sprintf("%sexpected %s %s %s", - verdictFailPrefix, exp.Variable, exp.Operator, exp.Value)) - // An earlier statement may already have failed the test; never overwrite an // existing failure with a later assertion's result. fmt.Fprintf(b, " IF $Verdict = '%s' THEN\n", verdictPass) - if exp.Operator == "<>" { - fmt.Fprintf(b, " IF %s THEN\n", equal) - fmt.Fprintf(b, " SET $Verdict = '%s';\n", failMsg) - b.WriteString(" END IF;\n") - } else { - fmt.Fprintf(b, " IF %s THEN\n", equal) - b.WriteString(" ELSE\n") - fmt.Fprintf(b, " SET $Verdict = '%s';\n", failMsg) - b.WriteString(" END IF;\n") - } + fmt.Fprintf(b, " IF %s THEN\n", exp.Condition) + b.WriteString(" ELSE\n") + fmt.Fprintf(b, " SET $Verdict = %s;\n", failVerdictExpr(exp)) + b.WriteString(" END IF;\n") b.WriteString(" END IF;\n") } +// failVerdictExpr builds the MDL expression assigned to $Verdict when an +// assertion fails. +// +// When the observed value can be rendered as a String without guessing its type, +// it is concatenated onto the message. A failure that says only what was expected +// tells you nothing about what came back, which is half the value of a failing +// test. +func failVerdictExpr(exp Expect) string { + msg := verdictFailPrefix + "expected " + exp.Raw + if exp.Actual == "" { + return "'" + escapeMDLString(msg) + "'" + } + return "'" + escapeMDLString(msg+", actual: ") + "' + " + exp.Actual +} + // rewriteBodyForVerdict attaches an ON ERROR handler to every CALL in the test // body, turning a thrown error into a FAIL verdict and an early return. func rewriteBodyForVerdict(lines []string, tc TestCase) []string { diff --git a/cmd/mxcli/testrunner/generator_endpoint_test.go b/cmd/mxcli/testrunner/generator_endpoint_test.go index dedfd9ba7..64f7b644f 100644 --- a/cmd/mxcli/testrunner/generator_endpoint_test.go +++ b/cmd/mxcli/testrunner/generator_endpoint_test.go @@ -38,9 +38,9 @@ func TestGenerateTestFlowsNoVariableRenaming(t *testing.T) { suite := &TestSuite{ Tests: []TestCase{ {ID: "test_1", Name: "a", MDL: "$result = CALL MICROFLOW Mod.A();", - Expects: []Expect{{Variable: "$result", Operator: "=", Value: "'x'"}}}, + Expects: []Expect{expectOf("$result = 'x'")}}, {ID: "test_2", Name: "b", MDL: "$result = CALL MICROFLOW Mod.B();", - Expects: []Expect{{Variable: "$result", Operator: "=", Value: "'y'"}}}, + Expects: []Expect{expectOf("$result = 'y'")}}, }, } mdl := GenerateTestFlows(suite) @@ -56,7 +56,7 @@ func TestGenerateTestFlowsNoVariableRenaming(t *testing.T) { func TestGenerateTestFlowsExpectAssertion(t *testing.T) { suite := &TestSuite{Tests: []TestCase{{ ID: "test_1", Name: "equality", MDL: "$r = CALL MICROFLOW Mod.A();", - Expects: []Expect{{Variable: "$r", Operator: "=", Value: "'John'"}}, + Expects: []Expect{expectOf("$r = 'John'")}, }}} mdl := GenerateTestFlows(suite) @@ -68,21 +68,23 @@ func TestGenerateTestFlowsExpectAssertion(t *testing.T) { } } -// TestGenerateTestFlowsNotEqualIsCompiledAsEquality pins the inherited -// constraint: `<>` produced Mendix expression errors, so it must never reach the -// model — a <> expectation is the same equality with the branches swapped. -func TestGenerateTestFlowsNotEqualIsCompiledAsEquality(t *testing.T) { +// TestGenerateTestFlowsNotEqualIsRewritten pins the inherited constraint and the +// way it is now met. `<>` still must never reach the model — Mendix's expression +// engine rejects that spelling — but the branch-swapping workaround is gone: +// ParseExpect rewrites the operator to `!=`, so the condition is emitted as +// written and every other operator can be too. +func TestGenerateTestFlowsNotEqualIsRewritten(t *testing.T) { suite := &TestSuite{Tests: []TestCase{{ ID: "test_1", Name: "inequality", MDL: "$r = CALL MICROFLOW Mod.A();", - Expects: []Expect{{Variable: "$r", Operator: "<>", Value: "'John'"}}, + Expects: []Expect{expectOf("$r <> 'John'")}, }}} mdl := GenerateTestFlows(suite) if strings.Contains(mdl, "$r <> 'John'") { t.Error("the <> operator reached the generated Mendix expression") } - if !strings.Contains(mdl, "IF $r = 'John' THEN") { - t.Errorf("<> was not compiled as a swapped equality:\n%s", mdl) + if !strings.Contains(mdl, "IF $r != 'John' THEN") { + t.Errorf("<> was not rewritten to !=:\n%s", mdl) } } diff --git a/cmd/mxcli/testrunner/generator_test.go b/cmd/mxcli/testrunner/generator_test.go index 06d1c324a..4622a0676 100644 --- a/cmd/mxcli/testrunner/generator_test.go +++ b/cmd/mxcli/testrunner/generator_test.go @@ -49,7 +49,7 @@ func TestGenerateTestRunner_ParsesWhenTestUsesChangeAndListOps(t *testing.T) { "$count = CALL MICROFLOW MfTest.M051_AggregateCount(ProductList = $filtered);", }, "\n"), Expects: []Expect{ - {Variable: "$count", Operator: "=", Value: "1"}, + expectOf("$count = 1"), }, }, }, @@ -81,7 +81,7 @@ func TestGenerateTestRunner_RenamesAllAssignmentsInTestBlock(t *testing.T) { "ADD $product TO $list;", }, "\n"), Expects: []Expect{ - {Variable: "$result", Operator: "=", Value: "true"}, + expectOf("$result = true"), }, }, }, diff --git a/cmd/mxcli/testrunner/parser.go b/cmd/mxcli/testrunner/parser.go index aff53633b..c00e0074a 100644 --- a/cmd/mxcli/testrunner/parser.go +++ b/cmd/mxcli/testrunner/parser.go @@ -15,23 +15,21 @@ import ( // TestCase represents a single test extracted from a test file. type TestCase struct { - ID string // Generated test ID (test_1, test_2, ...) - Name string // From @test annotation - MDL string // Raw MDL statements for this test block - Expects []Expect // @expect assertions - Verify []string // @verify OQL queries - Setup string // @setup block reference - Cleanup string // @cleanup strategy ("rollback" or "none") - Throws string // @throws expected error message - SourceFile string // Original file path - Line int // Line number in source file -} - -// Expect represents an @expect assertion. -type Expect struct { - Variable string // $var or $var/Attr - Operator string // "=" or "<>" - Value string // Expected value as string literal + ID string // Generated test ID (test_1, test_2, ...) + Name string // From @test annotation + MDL string // Raw MDL statements for this test block + Expects []Expect // @expect assertions + // ExpectErrors holds one message per @expect the runner could not compile + // into an assertion. A test carrying any of these is reported as an ERROR + // and never run: an assertion that cannot be evaluated must not be able to + // report a pass. + ExpectErrors []string + Verify []string // @verify OQL queries + Setup string // @setup block reference + Cleanup string // @cleanup strategy ("rollback" or "none") + Throws string // @throws expected error message + SourceFile string // Original file path + Line int // Line number in source file } // TestSuite represents a collection of tests from one or more files. @@ -140,16 +138,17 @@ func parseMDLTests(content string, sourcePath string) ([]TestCase, error) { testID := fmt.Sprintf("test_%d", i+1) tests = append(tests, TestCase{ - ID: testID, - Name: annotations.Test, - MDL: strings.TrimSpace(body), - Expects: annotations.Expects, - Verify: annotations.Verify, - Setup: annotations.Setup, - Cleanup: annotations.Cleanup, - Throws: annotations.Throws, - SourceFile: sourcePath, - Line: line, + ID: testID, + Name: annotations.Test, + MDL: strings.TrimSpace(body), + Expects: annotations.Expects, + ExpectErrors: annotations.ExpectErrors, + Verify: annotations.Verify, + Setup: annotations.Setup, + Cleanup: annotations.Cleanup, + Throws: annotations.Throws, + SourceFile: sourcePath, + Line: line, }) } @@ -202,16 +201,17 @@ func parseMarkdownTests(content string, sourcePath string) ([]TestCase, error) { } tests = append(tests, TestCase{ - ID: testID, - Name: name, - MDL: strings.TrimSpace(body), - Expects: annotations.Expects, - Verify: annotations.Verify, - Setup: annotations.Setup, - Cleanup: annotations.Cleanup, - Throws: annotations.Throws, - SourceFile: sourcePath, - Line: blockStart, + ID: testID, + Name: name, + MDL: strings.TrimSpace(body), + Expects: annotations.Expects, + ExpectErrors: annotations.ExpectErrors, + Verify: annotations.Verify, + Setup: annotations.Setup, + Cleanup: annotations.Cleanup, + Throws: annotations.Throws, + SourceFile: sourcePath, + Line: blockStart, }) } else { blockLines = append(blockLines, line) @@ -279,16 +279,22 @@ func extractDocAndBody(block string, fullContent string) (string, string, int) { // annotations holds parsed javadoc annotations for a test block. type annotations struct { - Test string - Expects []Expect - Verify []string - Setup string - Cleanup string - Throws string + Test string + Expects []Expect + ExpectErrors []string + Verify []string + Setup string + Cleanup string + Throws string } var ( - expectPattern = regexp.MustCompile(`@expect\s+(\$\S+)\s*(=|<>)\s*(.+)`) + // expectPattern captures the whole annotation body rather than a fixed + // operand/operator/operand shape. Matching a shape is what made this silent: + // a line the pattern did not fit produced no assertion at all, and a test + // with no assertions passes. Everything after @expect is now handed to + // ParseExpect, which either compiles it or reports why it could not. + expectPattern = regexp.MustCompile(`@expect\s+(.+)`) verifyPattern = regexp.MustCompile(`@verify\s+(.+)`) testPattern = regexp.MustCompile(`@test\s+(.+)`) setupPattern = regexp.MustCompile(`@setup\s+(\S+)`) @@ -318,11 +324,12 @@ func parseAnnotations(doc string) annotations { a.Test = strings.TrimSpace(m[1]) } if m := expectPattern.FindStringSubmatch(line); m != nil { - a.Expects = append(a.Expects, Expect{ - Variable: strings.TrimSpace(m[1]), - Operator: strings.TrimSpace(m[2]), - Value: strings.TrimSpace(m[3]), - }) + exp, err := ParseExpect(m[1]) + if err != nil { + a.ExpectErrors = append(a.ExpectErrors, err.Error()) + } else { + a.Expects = append(a.Expects, exp) + } } if m := verifyPattern.FindStringSubmatch(line); m != nil { a.Verify = append(a.Verify, strings.TrimSpace(m[1])) diff --git a/cmd/mxcli/testrunner/parser_test.go b/cmd/mxcli/testrunner/parser_test.go index 8827fe9f1..881cc14a9 100644 --- a/cmd/mxcli/testrunner/parser_test.go +++ b/cmd/mxcli/testrunner/parser_test.go @@ -25,17 +25,11 @@ func TestParseAnnotations(t *testing.T) { if len(a.Expects) != 2 { t.Fatalf("Expects count: got %d, want 2", len(a.Expects)) } - if a.Expects[0].Variable != "$result" { - t.Errorf("Expect[0] variable: got %q, want %q", a.Expects[0].Variable, "$result") + if a.Expects[0].Condition != "$result = 'John Doe'" { + t.Errorf("Expect[0] condition: got %q, want %q", a.Expects[0].Condition, "$result = 'John Doe'") } - if a.Expects[0].Operator != "=" { - t.Errorf("Expect[0] operator: got %q, want %q", a.Expects[0].Operator, "=") - } - if a.Expects[0].Value != "'John Doe'" { - t.Errorf("Expect[0] value: got %q, want %q", a.Expects[0].Value, "'John Doe'") - } - if a.Expects[1].Variable != "$product/Name" { - t.Errorf("Expect[1] variable: got %q, want %q", a.Expects[1].Variable, "$product/Name") + if a.Expects[1].Condition != "$product/Name = 'TestProduct'" { + t.Errorf("Expect[1] condition: got %q, want %q", a.Expects[1].Condition, "$product/Name = 'TestProduct'") } if len(a.Verify) != 1 { t.Fatalf("Verify count: got %d, want 1", len(a.Verify)) @@ -161,7 +155,7 @@ func TestGenerateTestRunner(t *testing.T) { Name: "Hello World", MDL: "$result = CALL MICROFLOW MfTest.M001_HelloWorld();", Expects: []Expect{ - {Variable: "$result", Operator: "=", Value: "true"}, + expectOf("$result = true"), }, }, { @@ -169,7 +163,7 @@ func TestGenerateTestRunner(t *testing.T) { Name: "String concat", MDL: "$result = CALL MICROFLOW MfTest.M003(FirstName = 'John', LastName = 'Doe');", Expects: []Expect{ - {Variable: "$result", Operator: "=", Value: "'John Doe'"}, + expectOf("$result = 'John Doe'"), }, }, }, diff --git a/cmd/mxcli/testrunner/results.go b/cmd/mxcli/testrunner/results.go index eeb968e20..885010d80 100644 --- a/cmd/mxcli/testrunner/results.go +++ b/cmd/mxcli/testrunner/results.go @@ -74,6 +74,22 @@ func (sr *SuiteResult) FailCount() int { return n } +// ErrorCount returns the number of tests that did not reach a verdict — an +// uncompilable @expect, a missing microflow, a failed request. +// +// It is reported separately from FailCount in the summary line. A suite whose +// output cannot distinguish "this assertion is false" from "this assertion was +// never evaluated" is how the silent-pass defect stayed invisible for weeks. +func (sr *SuiteResult) ErrorCount() int { + n := 0 + for _, t := range sr.Tests { + if t.Status == StatusError { + n++ + } + } + return n +} + // SkipCount returns the number of skipped tests. func (sr *SuiteResult) SkipCount() int { n := 0 @@ -210,6 +226,13 @@ func ParseLogResults(logReader io.Reader, suite *TestSuite) *SuiteResult { // Collect results in test order for _, tc := range suite.Tests { + // A test whose @expect did not compile was never generated, so the log + // has nothing to say about it. Report the parse error rather than the + // generic "not executed". + if res, bad := expectErrorResult(tc); bad { + result.Tests = append(result.Tests, res) + continue + } if r, ok := resultMap[tc.ID]; ok { // Use the original test name if available if tc.Name != "" { @@ -265,8 +288,13 @@ func PrintResults(w io.Writer, result *SuiteResult, color bool) { } fmt.Fprintf(w, "%s\n", strings.Repeat("-", 60)) - fmt.Fprintf(w, "Total: %d Passed: %d Failed: %d Skipped: %d", - len(result.Tests), result.PassCount(), result.FailCount(), result.SkipCount()) + errors := result.ErrorCount() + fmt.Fprintf(w, "Total: %d Passed: %d Failed: %d", + len(result.Tests), result.PassCount(), result.FailCount()-errors) + if errors > 0 { + fmt.Fprintf(w, " Errors: %d", errors) + } + fmt.Fprintf(w, " Skipped: %d", result.SkipCount()) if result.Duration > 0 { fmt.Fprintf(w, " Time: %s", result.Duration.Round(time.Millisecond)) } @@ -286,3 +314,24 @@ func PrintResults(w io.Writer, result *SuiteResult, color bool) { } } } + +// expectErrorResult turns a test whose @expect could not be compiled into an +// ERROR result. +// +// This is the fail-closed rule the whole @expect pipeline is built around: an +// assertion the runner cannot evaluate must never be able to report a pass. The +// previous implementation dropped such a line during parsing, and a test with no +// assertions left passes as long as it does not throw — so a suite could report +// green while asserting nothing. ERROR is counted with the failures, so the run's +// exit code is non-zero. +func expectErrorResult(tc TestCase) (TestResult, bool) { + if len(tc.ExpectErrors) == 0 { + return TestResult{}, false + } + return TestResult{ + ID: tc.ID, + Name: tc.Name, + Status: StatusError, + Message: strings.Join(tc.ExpectErrors, "; "), + }, true +} diff --git a/cmd/mxcli/testrunner/runner.go b/cmd/mxcli/testrunner/runner.go index 0113fae72..c9b3ef04a 100644 --- a/cmd/mxcli/testrunner/runner.go +++ b/cmd/mxcli/testrunner/runner.go @@ -375,10 +375,11 @@ func ListTests(files []string, w io.Writer) error { fmt.Fprintf(w, "Found %d test(s):\n", len(suite.Tests)) for _, tc := range suite.Tests { fmt.Fprintf(w, " %s: %s\n", tc.ID, tc.Name) - if len(tc.Expects) > 0 { - for _, exp := range tc.Expects { - fmt.Fprintf(w, " @expect %s %s %s\n", exp.Variable, exp.Operator, exp.Value) - } + for _, exp := range tc.Expects { + fmt.Fprintf(w, " @expect %s\n", exp.Raw) + } + for _, e := range tc.ExpectErrors { + fmt.Fprintf(w, " ERROR: %s\n", e) } if tc.Throws != "" { fmt.Fprintf(w, " @throws '%s'\n", tc.Throws) diff --git a/cmd/mxcli/testrunner/runner_endpoint.go b/cmd/mxcli/testrunner/runner_endpoint.go index e2df68e4b..17b79046f 100644 --- a/cmd/mxcli/testrunner/runner_endpoint.go +++ b/cmd/mxcli/testrunner/runner_endpoint.go @@ -120,6 +120,11 @@ func runSuite(client *endpointClient, suite *TestSuite, opts RunOptions, w io.Wr leaked := 0 for _, tc := range suite.Tests { + if res, bad := expectErrorResult(tc); bad { + result.Tests = append(result.Tests, res) + continue + } + flow := testFlowName(tc) if !present[flow] { result.Tests = append(result.Tests, TestResult{ diff --git a/docs-site/src/tools/running-tests.md b/docs-site/src/tools/running-tests.md index 1c91bb007..39922074a 100644 --- a/docs-site/src/tools/running-tests.md +++ b/docs-site/src/tools/running-tests.md @@ -118,6 +118,50 @@ every request must present that token, non-loopback callers are refused, and it will only ever invoke the generated `MxTest.Test_*` microflows. The token is never written into your project. +### `@expect`: what an assertion may say, and what happens when it cannot + +An `@expect` is a **Mendix expression that must evaluate to true**. Any +expression the Mendix engine accepts works — built-in functions, every +comparison operator, `and` / `or` / `not(...)`, attribute paths and enumeration +values: + +```mdl +/** + * @test the dealt board is a full grid with blanks + * @expect length($result) = 81 + * @expect find($result, '0') >= 0 + * @expect substring($result, 0, 9) != substring($result, 9, 18) + */ +$result = CALL MICROFLOW Sudoku.SUB_BlankSquares(Grid = $solved); +/ +``` + +`<>` is accepted and rewritten to `!=`, which is the spelling Mendix's +expression engine accepts — `<>` fails the build with CE0117. + +**An assertion the runner cannot compile is an ERROR, not a pass.** Unknown +functions, wrong arity, unbalanced parentheses, and expressions that produce a +value rather than a condition are each reported against the test that carries +them, and an ERROR counts with the failures, so the run exits non-zero: + +``` +ERROR a self-evident falsehood + @expect randomInt($result) = 1: randomInt() is not a Mendix expression + function at column 1 ("randomInt") +``` + +A failing assertion reports **what came back**, not only what was wanted: + +``` +FAIL the board is 81 squares + expected length($result) = 81, actual: 27 +``` + +The observed value is omitted rather than guessed when nothing in the assertion +pins down its type (`@expect $a = $b`, where both sides are variables). Mendix's +expression engine is typed, and a wrong guess would fail the build rather than +the test. + ### The app's own after-startup microflow Boot registers the endpoint and then runs the project's own after-startup diff --git a/mdl-examples/bug-tests/expect-vacuous-assertions.mdl b/mdl-examples/bug-tests/expect-vacuous-assertions.mdl new file mode 100644 index 000000000..c479c3d10 --- /dev/null +++ b/mdl-examples/bug-tests/expect-vacuous-assertions.mdl @@ -0,0 +1,190 @@ +-- FINDINGS #46 (mxcli-sudoku): `mxcli test` silently passed any @expect it could +-- not evaluate. +-- +-- The runner matched one assertion shape with a regular expression: +-- +-- @expect\s+(\$\S+)\s*(=|<>)\s*(.+) +-- +-- A line that did not fit produced no assertion at all — not a warning, not an +-- error, nothing — and a test with zero assertions passes as long as its body +-- does not throw. So this reported PASS: +-- +-- /** +-- * @test a self-evident falsehood +-- * @expect 1 = 2 +-- */ +-- $result = CALL MICROFLOW MyModule.Anything(); +-- +-- and so did `length($result) = 999`, `find($result, 'Z') >= 0` with the needle +-- absent, `substring($result, 0, 1) = 'Z'`, and `$result != `. +-- In the reporting project 16 of 22 tests asserted nothing beyond "did not +-- throw", and the suite had said 22/22 at every commit. That is the worst +-- failure mode a test framework has: it does not cost you time, it certifies +-- unverified work as verified. +-- +-- The defect was never the narrow support — it was the silence. An @expect the +-- runner cannot compile is now an ERROR against that test, the test is not +-- generated at all, and ERROR counts with the failures so the run exits +-- non-zero. Everything Mendix's expression engine can evaluate is now accepted: +-- built-ins, every comparison operator, and/or/not. +-- +-- This script is the model-side half of the repro. It creates the microflows the +-- generated tests would produce for each assertion shape in the finding's table, +-- so `mx check` can confirm the emitted expressions are valid Mendix. Run: +-- +-- mxcli exec mdl-examples/bug-tests/expect-vacuous-assertions.mdl -p app.mpr +-- mxcli docker check -p app.mpr -- expect 0 errors +-- +-- The Go-side half is TestExpectCanariesAreEvaluated in +-- cmd/mxcli/testrunner/expect_test.go: stub ParseExpect back to the old regex +-- and every canary below returns an empty condition again. +-- +-- Two facts measured against mxbuild 11.6.6 while fixing this, both of which the +-- generated MDL depends on: +-- +-- * `<>` really is rejected — CE0117 "Error(s) in expression" — so rewriting +-- it to `!=` is load-bearing, not a style choice. +-- * a wrongly typed comparison really is caught (`$result = 3` on a String is +-- CE0117), which is what makes a 0-error run on the shapes below evidence +-- rather than an absence of checking. + +CREATE MODULE MxExpectRepro; + +-- Each microflow below is one row of the finding's "which shapes are real" +-- table, in the form the test runner generates: assert, and on failure report +-- both the expectation and the value that came back. + +CREATE OR REPLACE MICROFLOW MxExpectRepro.Canary_Equality () +RETURNS String AS $Verdict +BEGIN + DECLARE $Verdict String = 'PASS'; + DECLARE $result String = 'x'; + IF $result = 'John' THEN + ELSE + SET $Verdict = 'FAIL:expected $result = ''John'', actual: ' + $result; + END IF; + RETURN $Verdict; +END; +/ + +-- `<>` in the annotation, `!=` in the model. The other spelling is CE0117. +CREATE OR REPLACE MICROFLOW MxExpectRepro.Canary_Inequality () +RETURNS String AS $Verdict +BEGIN + DECLARE $Verdict String = 'PASS'; + DECLARE $result String = 'x'; + IF $result != 'John' THEN + ELSE + SET $Verdict = 'FAIL:expected $result <> ''John'', actual: ' + $result; + END IF; + RETURN $Verdict; +END; +/ + +-- length() returns Integer, so the observed value is wrapped in toString(). +CREATE OR REPLACE MICROFLOW MxExpectRepro.Canary_Length () +RETURNS String AS $Verdict +BEGIN + DECLARE $Verdict String = 'PASS'; + DECLARE $result String = 'x'; + IF length($result) = 81 THEN + ELSE + SET $Verdict = 'FAIL:expected length($result) = 81, actual: ' + toString(length($result)); + END IF; + RETURN $Verdict; +END; +/ + +CREATE OR REPLACE MICROFLOW MxExpectRepro.Canary_Find () +RETURNS String AS $Verdict +BEGIN + DECLARE $Verdict String = 'PASS'; + DECLARE $result String = 'x'; + IF find($result, '0') >= 0 THEN + ELSE + SET $Verdict = 'FAIL:expected find($result, ''0'') >= 0, actual: ' + toString(find($result, '0')); + END IF; + RETURN $Verdict; +END; +/ + +-- substring() returns String, so no wrapping. The observed side is the left one. +CREATE OR REPLACE MICROFLOW MxExpectRepro.Canary_Substring () +RETURNS String AS $Verdict +BEGIN + DECLARE $Verdict String = 'PASS'; + DECLARE $result String = 'x'; + IF substring($result, 0, 1) = substring($result, 1, 2) THEN + ELSE + SET $Verdict = 'FAIL:expected substring($result, 0, 1) = substring($result, 1, 2), actual: ' + substring($result, 0, 1); + END IF; + RETURN $Verdict; +END; +/ + +-- The contradiction from the finding: it passed as one expression, and must not. +CREATE OR REPLACE MICROFLOW MxExpectRepro.Canary_Contradiction () +RETURNS String AS $Verdict +BEGIN + DECLARE $Verdict String = 'PASS'; + DECLARE $result String = 'x'; + IF find($result, '0') >= 0 and find($result, '0') < 0 THEN + ELSE + SET $Verdict = 'FAIL:expected find($result, ''0'') >= 0 and find($result, ''0'') < 0'; + END IF; + RETURN $Verdict; +END; +/ + +-- Neither side is observed, so no actual value is reported. +CREATE OR REPLACE MICROFLOW MxExpectRepro.Canary_Falsehood () +RETURNS String AS $Verdict +BEGIN + DECLARE $Verdict String = 'PASS'; + IF 1 = 2 THEN + ELSE + SET $Verdict = 'FAIL:expected 1 = 2'; + END IF; + RETURN $Verdict; +END; +/ + +-- A Boolean literal on the right pins the left side's type. +CREATE OR REPLACE MICROFLOW MxExpectRepro.Canary_Boolean () +RETURNS String AS $Verdict +BEGIN + DECLARE $Verdict String = 'PASS'; + DECLARE $done Boolean = true; + IF $done = true THEN + ELSE + SET $Verdict = 'FAIL:expected $done = true, actual: ' + toString($done); + END IF; + RETURN $Verdict; +END; +/ + +CREATE OR REPLACE MICROFLOW MxExpectRepro.Canary_Not () +RETURNS String AS $Verdict +BEGIN + DECLARE $Verdict String = 'PASS'; + DECLARE $done Boolean = true; + IF not($done) THEN + ELSE + SET $Verdict = 'FAIL:expected not($done)'; + END IF; + RETURN $Verdict; +END; +/ + +CREATE OR REPLACE MICROFLOW MxExpectRepro.Canary_NestedCalls () +RETURNS String AS $Verdict +BEGIN + DECLARE $Verdict String = 'PASS'; + DECLARE $result String = 'x'; + IF trim(toUpperCase($result)) != 'X' THEN + ELSE + SET $Verdict = 'FAIL:expected trim(toUpperCase($result)) != ''X'', actual: ' + trim(toUpperCase($result)); + END IF; + RETURN $Verdict; +END; +/ From c48ee0915944eb8ccb1be62595ce2119c383be5c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Aug 2026 22:11:35 +0000 Subject: [PATCH 08/22] Keep the #891/#892 repros out of the check-time negative harness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI failed with both new fixtures reporting "negative test unexpectedly passed". The guards are correct; the fixtures are in the wrong harness. `.fail.mdl` has one meaning in `make check-mdl`: the runner executes `mxcli check ` with NO project and demands a non-zero exit. That tests check-time rules only. Both of these guards need a model before they can decide anything — DROP FOLDER's emptiness check lives in mdl/executor/cmd_folders.go, and the bare-column-target refusal in mdl/backend/pagemutator/mutator.go. The statements themselves are valid MDL, so `check` exits 0 and the runner reads that as the rule having regressed. Verified against the branch's own binary: both exit 0, and 892 reports "Syntax OK (1 statements) / Check passed!". Renaming to plain .mdl fixes it. Nothing is weakened: the refusals are covered by the unit tests this PR already added (cmd_folders_mock_test.go, mutator_column_addressing_test.go), and the files stay as the by-hand repro, which is what their own comments describe running. Confirmed against a real 11.12.1 app that the fix still does its job after the rename: list folders in FeedbackModule -> Private/Resources/Mappings [4] exec 892-drop-folder-not-empty.mdl -> refused, naming the 4 documents The two header comments said "expected to FAIL", which was true of the old suffix and would now mislead the next reader, so each says instead why it is not a .fail.mdl and points at its unit coverage. The same note goes in the Makefile beside the convention, since the trap is the convention's own edge rather than something either author did wrong. `make check-mdl` is green (both files PASS) and `go test ./...` passes. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4 --- Makefile | 7 +++++++ ...l => 891-alter-page-bare-column-target.mdl} | 18 ++++++++++++++++-- ....fail.mdl => 892-drop-folder-not-empty.mdl} | 17 ++++++++++++++--- 3 files changed, 37 insertions(+), 5 deletions(-) rename mdl-examples/bug-tests/{891-alter-page-bare-column-target.fail.mdl => 891-alter-page-bare-column-target.mdl} (69%) rename mdl-examples/bug-tests/{892-drop-folder-not-empty.fail.mdl => 892-drop-folder-not-empty.mdl} (65%) diff --git a/Makefile b/Makefile index 97c55fce3..655c49899 100644 --- a/Makefile +++ b/Makefile @@ -176,6 +176,13 @@ engine-diff: grammar # `mxcli check` (the script reproduces a symptom that a new validation # rule rejects). The runner inverts the exit code for these: an unexpected # pass is treated as a regression of the rule. +# +# `check` runs here WITHOUT a project, so only CHECK-TIME rules can be tested +# this way. A guard living in the executor or a backend needs a model before it +# can decide anything, so its repro is valid MDL, `check` exits 0, and naming +# that file .fail.mdl reports "negative test unexpectedly passed" — a working +# rule made to look regressed (#891, #892). Keep those repros as plain .mdl and +# cover the guard with a unit test. check-mdl: build @FAILED=0; \ for f in mdl-examples/doctype-tests/*.mdl mdl-examples/bug-tests/*.mdl; do \ diff --git a/mdl-examples/bug-tests/891-alter-page-bare-column-target.fail.mdl b/mdl-examples/bug-tests/891-alter-page-bare-column-target.mdl similarity index 69% rename from mdl-examples/bug-tests/891-alter-page-bare-column-target.fail.mdl rename to mdl-examples/bug-tests/891-alter-page-bare-column-target.mdl index 345c614f4..275bef855 100644 --- a/mdl-examples/bug-tests/891-alter-page-bare-column-target.fail.mdl +++ b/mdl-examples/bug-tests/891-alter-page-bare-column-target.mdl @@ -32,8 +32,22 @@ -- DESCRIBE PAGE masked it by skipping the malformed node: REPLACE looked like a -- clean deletion, INSERT looked like a harmless no-op. Neither was. -- --- This file is expected to FAIL — the refusal is the fix. The qualified forms --- `grid1.NextRunAt` / `grid1.PageSize` work and are what to use instead. +-- Both statements below are REFUSED once applied to a project — that refusal is +-- the fix. The qualified forms `grid1.NextRunAt` / `grid1.PageSize` work and are +-- what to use instead. +-- +-- NOT a `.fail.mdl`, deliberately. That suffix means "must fail `mxcli check`", +-- and the harness in `make check-mdl` runs `mxcli check ` with **no +-- project**. This guard is exec-time and project-dependent: the statements below +-- are valid MDL, and whether they are safe can only be decided against a real +-- model. So `check` exits 0 and a `.fail.mdl` here reports +-- "negative test unexpectedly passed" — the rule looking regressed when it is +-- working. The refusal itself is covered by unit tests; this file is the repro +-- you run by hand, against a project. +-- +-- mxcli exec 891-alter-page-bare-column-target.mdl -p .mpr # refused +-- +-- Unit coverage: mdl/backend/pagemutator/mutator_column_addressing_test.go ALTER PAGE P91.SyncConfiguration_Overview { REPLACE NextRunAt WITH { diff --git a/mdl-examples/bug-tests/892-drop-folder-not-empty.fail.mdl b/mdl-examples/bug-tests/892-drop-folder-not-empty.mdl similarity index 65% rename from mdl-examples/bug-tests/892-drop-folder-not-empty.fail.mdl rename to mdl-examples/bug-tests/892-drop-folder-not-empty.mdl index 297fbea5e..c88a00ac4 100644 --- a/mdl-examples/bug-tests/892-drop-folder-not-empty.fail.mdl +++ b/mdl-examples/bug-tests/892-drop-folder-not-empty.mdl @@ -17,12 +17,23 @@ -- [error] [CE1613] "The selected export mapping -- 'FeedbackModule.EXM_PostFeedback' no longer exists." -- --- After the fix the DROP is refused and the four documents are untouched. --- This file is expected to FAIL — that refusal is the fix. +-- After the fix the DROP is refused and the four documents are untouched. That +-- refusal is the fix. -- --- mxcli exec 892-drop-folder-not-empty.fail.mdl -p .mpr +-- NOT a `.fail.mdl`, deliberately. That suffix means "must fail `mxcli check`", +-- and the harness in `make check-mdl` runs `mxcli check ` with **no +-- project**. This guard is exec-time and project-dependent: the statements below +-- are valid MDL, and whether they are safe can only be decided against a real +-- model. So `check` exits 0 and a `.fail.mdl` here reports +-- "negative test unexpectedly passed" — the rule looking regressed when it is +-- working. The refusal itself is covered by unit tests; this file is the repro +-- you run by hand, against a project. +-- +-- mxcli exec 892-drop-folder-not-empty.mdl -p .mpr -- mx check .mpr # 0 errors, documents intact -- +-- Unit coverage: mdl/executor/cmd_folders_mock_test.go +-- -- Run `list folders in FeedbackModule` first: the folder must report [4], not -- [0]. A [0] there is the listing half of this bug and makes the drop below -- look safe. From 3cc8e5672275ba508c70bf442e104a5a60daff09 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 07:47:55 +0000 Subject: [PATCH 09/22] mxcli test: report what each test actually asserted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the @expect fail-closed fix (#151). That change stopped an assertion from being silently dropped. This one closes the two remaining silent-absence paths, both of which produce the same result: a green suite nobody can read. **A test that asserts nothing looked exactly like one that asserts six.** A test with no @expect and no @throws returns PASS the moment its body completes. That is a legitimate smoke test, but after the @expect fix the cheapest way back to a green suite is to delete the assertion — and the output could not tell that apart from a repair. Every result line now carries the count, and a run containing a vacuous test says so: PASS the board is 81 squares (6ms, 2 assertions) PASS asserts nothing at all (4ms, no assertions) ------------------------------------------------------------ 1 test(s) asserted nothing beyond "did not throw". Run with --require-assertions to make that an error. The count is on the ordinary result line rather than behind --verbose, because the lesson of the original defect is that the *default* output has to distinguish a test that asserted from one that did not. Vacuous tests still pass by default; --require-assertions makes them ERROR for a project that has decided every test must assert. **@verify is parsed and evaluated by nothing.** It is documented in the skill's annotation table as an OQL post-condition, has been populated into TestCase since the runner was written, and is read by nothing but --list — a test whose only assertion was a @verify asserted nothing. That is the same defect as a dropped @expect wearing a different annotation, so it gets the same answer: an ERROR naming the annotation and pointing at @expect. The docs no longer advertise it as working. Also: TestResult now carries SourceFile, so JUnit's classname and file identify the test file instead of stamping every case with the suite name — a failure in a multi-file run could not say where it lived — and the assertion count rides along as a a CI report can show. Every TestResult is now built through one constructor (newResult), which carries the case-derived fields. The five literal construction sites were exactly how a new field gets populated in one path and silently missing in another. Controls: stubbing AssertionCount to return 1 and reverting the @verify rejection puts every new test in assertions_test.go back to failing with the reported symptom. Reported as mxcli-sudoku FINDINGS #46. Repro: mdl-examples/bug-tests/expect-vacuous-assertions.test.mdl Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01CqQLyxnppZGfqPYbUHvSWG --- .claude/skills/fix-issue.md | 1 + .claude/skills/mendix/test-microflows.md | 45 +++++- cmd/mxcli/cmd_test_run.go | 33 +++-- cmd/mxcli/main.go | 2 + cmd/mxcli/syntax/features_misc.go | 5 + cmd/mxcli/testrunner/assertions_test.go | 117 +++++++++++++++ cmd/mxcli/testrunner/client.go | 7 +- cmd/mxcli/testrunner/expect_test.go | 24 ++-- cmd/mxcli/testrunner/generator.go | 2 +- cmd/mxcli/testrunner/generator_endpoint.go | 2 +- cmd/mxcli/testrunner/junit.go | 53 +++++-- cmd/mxcli/testrunner/parser.go | 101 ++++++++----- cmd/mxcli/testrunner/results.go | 136 ++++++++++++++---- cmd/mxcli/testrunner/runner.go | 17 ++- cmd/mxcli/testrunner/runner_endpoint.go | 26 ++-- docs-site/src/tools/running-tests.md | 30 ++++ .../expect-vacuous-assertions.test.mdl | 59 ++++++++ 17 files changed, 536 insertions(+), 124 deletions(-) create mode 100644 cmd/mxcli/testrunner/assertions_test.go create mode 100644 mdl-examples/bug-tests/expect-vacuous-assertions.test.mdl diff --git a/.claude/skills/fix-issue.md b/.claude/skills/fix-issue.md index 086d452e5..38f6d0159 100644 --- a/.claude/skills/fix-issue.md +++ b/.claude/skills/fix-issue.md @@ -520,4 +520,5 @@ extracting `OffsetExpression`/`LimitExpression`. | `DESCRIBE PAGE` renders an **Accordion group** (or any pluggable widget's object-list item) **empty** — the group's own properties print, its nested widgets do not — so a describe→exec round-trip silently DELETES whatever was inside | An object-list item can carry child widgets in a **Widgets-typed sub-property** (the group's `content` / `headerContent` slot). `extractObjectListItem` handled only scalar sub-properties (datasource, attribute, expression, text template, primitive) and fell through on everything else, so children were never read; and the emitter always closed an item with `"\n"`, so they had nowhere to go even once read. Both halves must change — reading without emitting still prints an empty group | `mdl/executor/cmd_pages_describe_objectlist.go` (`rawObjectListItem.Children`, `extractObjectListItem`), `mdl/executor/cmd_pages_describe_output.go` (the object-list item loop) | Parse a `Widgets` array with `parseRawWidget` — the same recursion the rest of DESCRIBE uses — and emit the item with a `{ … }` body, recursing through `outputWidgetMDLV3` so nesting and indentation stay consistent. Also relax the "keep this item" test to include `len(item.Children) > 0`, or a group whose only content is widgets is dropped wholesale. **The Accordion ships in every blank app** (`widgets/com.mendix.widget.web.Accordion.mpk`) — do not conclude it needs a marketplace install because `modelsdk/widgets/definitions/` has no `accordion.def.json`; that is mxcli's *bundled* registry, while project widgets are discovered from the MPK into `.mxcli/widgets/`. Author one with `PLUGGABLEWIDGET 'com.mendix.widget.web.accordion.Accordion'` (`ACCORDION` is the catalog's MdlName but not a parser keyword). Repro `mdl-examples/bug-tests/891-accordion-group-nested-widgets.mdl`. Issue #891 | | A microflow computes a **different number than the expression says**, while `mxcli check`, `mx check` and the build are all green. An additive chain comes back from `DESCRIBE MICROFLOW` with its `+` and `-` exchanged — `$A - $B + 1` stored as `$A + $B - 1`. All-plus and all-minus chains are fine, as is `-` against `*` | `buildAdditiveExpression` read `AllPLUS()` and `AllMINUS()` as two separate token lists and emitted **every plus before every minus**, discarding source order. The precise rule is "the chain is re-sorted, all `+` ahead of all `-`" — sharper than "a `-` followed by a `+` swaps", and it predicts which cases survive | `mdl/visitor/visitor_microflow_expression.go` (`buildAdditiveExpression`) | **The fix already existed 20 lines below**: `buildMultiplicativeExpression` walks `GetChildren()` in order and builds its operator list correctly, so the additive case is that pattern copied — no new mechanism. The corruption is in the **stored model**, not in DESCRIBE: `strings` on the `.mxunit` shows the swapped text, which is why the running app computes it. A rewritten expression is perfectly valid, so no validator can catch this class — the only test that works is round-trip equality, not "does it apply cleanly". **The control cases carry the weight**: `$A - $B - 1` and `$A + $B - 1` pass both before and after, so a test built only from failing cases would have passed against code that sorted all minuses first instead. Verified by reverting the fix and confirming exactly the four swapped cases fail. Test `mdl/visitor/visitor_additive_order_test.go`; example `mdl-examples/bug-tests/additive-operator-order.mdl`. Reported in mxcli-ledger FINDINGS #105 | | `mxcli test` reports **PASS for an assertion that must fail** — `@expect 1 = 2`, `@expect length($result) = 999`, `@expect find($result, 'Z') >= 0` with the needle absent. Nothing in the output distinguishes a real assertion from a vacuous one, so a suite certifies work as verified while asserting only that the microflow did not throw. Mutation testing is what exposes it: mutants that return an obviously wrong value survive the suite | The `@expect` annotation was matched with a regex for one shape — `@expect $var (=|<>) ` — and `FindStringSubmatch` returning nil produced **no assertion at all** rather than an error. A test with zero assertions passes if its body completes. So the narrow support was not the defect; the silence was | `cmd/mxcli/testrunner/parser.go` (`expectPattern`, `parseAnnotations`), `cmd/mxcli/testrunner/expect.go` (new — `ParseExpect`, the validating parser), `cmd/mxcli/testrunner/generator_endpoint.go` + `generator.go` (emit the condition, not a rebuilt equality), `cmd/mxcli/testrunner/results.go` (`expectErrorResult`) | Capture the **whole** annotation body and hand it to a validating parser; anything it cannot compile becomes an `ExpectErrors` entry, the test is not generated at all, and the runner reports `StatusError` (which `FailCount` counts, so the exit code is non-zero). The parser is a strict recursive-descent pass over `exprcheck.Lex` — **not** `mdl/exprcheck`'s own parser, which recovers and emits hints, exactly the wrong behaviour here. Two measurements pinned the emitted expression against mxbuild 11.6.6: `<>` really is CE0117 (so the rewrite to `!=` is load-bearing, not cosmetic) and a wrong-typed comparison really is caught (`$result = 3` → CE0117), which is what makes the 0-error run on the 11 generated shapes mean something. **Generalisable**: when a pattern-matching parser can match *less* than its input, the non-match branch is a silent-failure path — audit every `if m := re.FindStringSubmatch(...); m != nil` whose else-branch does nothing. Repro `mdl-examples/bug-tests/expect-vacuous-assertions.mdl`. mxcli-sudoku FINDINGS #46 | +| A test suite's green is unreadable: a test that asserts **nothing** prints the same `PASS` as one with six assertions, and `@verify` — documented as an OQL post-condition — is parsed and evaluated by nothing at all. After @expect started failing closed, the cheapest way back to green is to delete the assertion, and the output cannot tell that apart from a repair | Two silent-absence paths rather than the silent-drop path fixed in the row above. `TestResult` carried no assertion count, so nothing downstream could report one; and `TestCase.Verify` was populated by `parseAnnotations` and read by nothing but `--list` — `grep -n '\.Verify' cmd/mxcli/testrunner/*.go` returns the parser and the lister, no runner | `cmd/mxcli/testrunner/results.go` (`TestResult.Assertions`/`SourceFile`, `newResult`, `vacuousResult`, `resultNote`, `VacuousCount`), `cmd/mxcli/testrunner/parser.go` (`AssertionCount`, `AssertionErrors`, the @verify rejection), `cmd/mxcli/testrunner/junit.go` (`junitClassName`, assertions property), `cmd/mxcli/main.go` + `cmd_test_run.go` (`--require-assertions`) | Count assertions on the test case and carry them onto every result through **one** constructor (`newResult`) — the previous code built `TestResult` literals at five sites, which is exactly how a new field gets populated in one path and silently missing in another. Report the count on the ordinary result line, not behind `--verbose`: the whole lesson of #46 is that the *default* output must distinguish a test that asserted from one that did not. Vacuous tests warn by default and error under `--require-assertions`, because a smoke test is legitimate but an indistinguishable one is not. **Generalisable**: when auditing an annotation/config field for dead ends, grep for *readers*, not writers — a field with a parser and no consumer is a feature the docs promise and the code does not deliver, and it fails silently by construction. mxcli-sudoku FINDINGS #46 (follow-up) | | Windows Defender flags the mxcli **Windows** release binary as `Trojan:Script/Sabsik.EN.A!ml`; enterprise EDR (Defender for Endpoint, CrowdStrike, SentinelOne) blocks it harder. Not the generic unsigned-Go-binary false positive of #185 | The binary genuinely embedded **chisel**, a dual-use tunnelling/pivoting tool (SSH over WebSocket), on every platform — although the tunnel only ever runs inside a Linux container. `run --hub` linked `chisel/client`, `tunnel-hub` linked `chisel/server`, so windows/darwin carried 32 packages incl. the whole `x/crypto/ssh` stack for a feature they cannot use | `cmd/mxcli/docker/tunnel_linux.go` + `tunnel_other.go` (client seam), `cmd/mxcli/tunnelhub/control_linux.go` + `control_other.go` (server seam), `scripts/check-tunnel-deps.sh` (guard) | **Never obfuscate, pack or rename to dodge the scanner** — attacker tradecraft, and it makes the binary less trustworthy, not more. **Code signing does not fix this class**: a signed binary containing chisel is still flagged behaviourally; signing only addresses #185's generic false positive. The fix is to stop shipping the capability where it is unused: one interface per seam, `_linux.go` impl + `!linux` stub, commands still registered everywhere but failing with an actionable message. **Prove absence three ways, and know that `go tool nm` is not one of them** — release ldflags `-s -w` strip the symbol table, so nm reports "no symbols" whether or not the code is linked and would give a false pass; use `go list -deps`, `go version -m`, and `strings` (nm only on a deliberately unstripped build). **Guard against the transitive path, not the name**: match the module list (`x/crypto/ssh`, `gorilla/websocket`, `armon/go-socks5`, `jpillora/*`) so re-entry without the word "chisel" still trips it, and assert a **positive control** (chisel IS in the linux graph) so the check cannot pass vacuously. Verified by re-adding the import and watching the guard fail on all four windows/darwin targets. Result: -13.5 MB (-14.7%) on windows+darwin, linux unchanged. See ADR-0009 | diff --git a/.claude/skills/mendix/test-microflows.md b/.claude/skills/mendix/test-microflows.md index b20c0d64e..7711d104e 100644 --- a/.claude/skills/mendix/test-microflows.md +++ b/.claude/skills/mendix/test-microflows.md @@ -155,7 +155,7 @@ The markdown format turns your tests into living documentation. | `@expect` | Assert a Mendix condition | `@expect $result = 'John Doe'` | | `@expect` | Assert an entity attribute | `@expect $product/Name = 'TestProduct'` | | `@expect` | Assert with a built-in | `@expect length($result) = 81` | -| `@verify` | OQL post-condition | `@verify select count(*) from Mod.E where Code = 'X' = 1` | +| `@verify` | **Not implemented** — rejected as an error | see below | | `@throws` | Expect error | `@throws 'validation failed'` | | `@cleanup` | Rollback strategy | `@cleanup rollback` (default) or `@cleanup none` | @@ -204,6 +204,49 @@ The value is omitted rather than guessed when neither side of the comparison establishes a type (`@expect $a = $b`), because Mendix's expression engine is typed and a wrong guess would break the build instead of the test. +### `@verify` is not implemented, and says so + +`@verify` was documented here as an OQL post-condition. It is parsed and **no +runner has ever evaluated one**, so a test whose only assertion was a `@verify` +asserted nothing. It is now rejected: + +``` +ERROR writes a row + @verify select count(*) …: @verify is not implemented — no runner + evaluates it, so it would assert nothing. Assert on the microflow's own + result with @expect instead +``` + +That is the same rule as for an uncompilable `@expect`, applied to the same +class of problem: an annotation that looks like an assertion and is silently +ignored is worse than one that is missing. To check a database post-condition +today, have the microflow under test return the value and assert on it with +`@expect`, or query the app separately with `mxcli oql`. + +### A test that asserts nothing says so + +Every result line carries what the test actually checked, and a run that +contains a vacuous test calls it out: + +``` + PASS the board is 81 squares (6ms, 2 assertions) + PASS asserts nothing at all (4ms, no assertions) +------------------------------------------------------------ +1 test(s) asserted nothing beyond "did not throw". Run with +--require-assertions to make that an error. +``` + +A test with no `@expect` and no `@throws` is a **smoke test** — it reports only +that the body did not throw. That is a legitimate thing to write, so it still +passes by default. What it may not do is look identical to a test with six +assertions: after `@expect` started failing closed, the cheapest way back to a +green suite is to delete the assertion, and that must not read as a repair. + +`--require-assertions` turns every vacuous test into an ERROR, for a project +that has decided each test must assert. The JUnit report carries the count as a +`` per case, and `classname` now identifies the +source file, so a failure in a multi-file run says where it lives. + ### `@cleanup` — what happens to a test's data **`rollback` is the default**, so by default a test's database writes do not diff --git a/cmd/mxcli/cmd_test_run.go b/cmd/mxcli/cmd_test_run.go index 2f7f0fc79..e31ce41ea 100644 --- a/cmd/mxcli/cmd_test_run.go +++ b/cmd/mxcli/cmd_test_run.go @@ -102,6 +102,9 @@ Examples: # Output JUnit XML for CI mxcli test tests/ -p app.mpr --junit results.xml + # Fail the run on any test that asserts nothing + mxcli test tests/ -p app.mpr --local --require-assertions + # List tests without executing mxcli test tests/ -p app.mpr --list @@ -128,6 +131,7 @@ Examples: projectPath, _ := cmd.Flags().GetString("project") list, _ := cmd.Flags().GetBool("list") junitOutput, _ := cmd.Flags().GetString("junit") + requireAssertions, _ := cmd.Flags().GetBool("require-assertions") skipBuild, _ := cmd.Flags().GetBool("skip-build") local, _ := cmd.Flags().GetBool("local") legacyRunner, _ := cmd.Flags().GetBool("legacy-runner") @@ -164,20 +168,21 @@ Examples: } opts := testrunner.RunOptions{ - ProjectPath: projectPath, - TestFiles: resolveTestPaths(args, projectPath), - SkipBuild: skipBuild, - Local: local, - LegacyRunner: legacyRunner, - Watch: watch, - Attach: attach, - SkipAppStartup: skipAppStartup, - Timeout: timeout, - JUnitOutput: junitOutput, - Verbose: verbose, - Color: color, - Stdout: os.Stdout, - Stderr: os.Stderr, + ProjectPath: projectPath, + TestFiles: resolveTestPaths(args, projectPath), + SkipBuild: skipBuild, + Local: local, + LegacyRunner: legacyRunner, + Watch: watch, + Attach: attach, + SkipAppStartup: skipAppStartup, + Timeout: timeout, + JUnitOutput: junitOutput, + RequireAssertions: requireAssertions, + Verbose: verbose, + Color: color, + Stdout: os.Stdout, + Stderr: os.Stderr, } // Only a --local run boots an app of its own, so only it decides which diff --git a/cmd/mxcli/main.go b/cmd/mxcli/main.go index b1320bd9e..e611ba9de 100644 --- a/cmd/mxcli/main.go +++ b/cmd/mxcli/main.go @@ -369,6 +369,8 @@ func init() { // Test command flags testRunCmd.Flags().BoolP("list", "l", false, "List tests without executing") testRunCmd.Flags().StringP("junit", "j", "", "Write JUnit XML results to file") + testRunCmd.Flags().Bool("require-assertions", false, + "Report a test that asserts nothing as an ERROR instead of a pass") testRunCmd.Flags().BoolP("skip-build", "s", false, "Skip build step (reuse existing deployment)") testRunCmd.Flags().Bool("local", false, "Run on mxcli's local runtime instead of Docker (no daemon needed)") testRunCmd.Flags().Bool("legacy-runner", false, "With --local, run tests from the after-startup microflow and parse the log, instead of over the test endpoint") diff --git a/cmd/mxcli/syntax/features_misc.go b/cmd/mxcli/syntax/features_misc.go index 2b4a2732b..d6fa7d893 100644 --- a/cmd/mxcli/syntax/features_misc.go +++ b/cmd/mxcli/syntax/features_misc.go @@ -601,6 +601,8 @@ Flags: With --local, do not run the project's own after-startup microflow (it runs by default) --legacy-runner With --local: use the old after-startup runner + --require-assertions + Report a test that asserts nothing as an ERROR -v, --verbose Show runtime log lines -t, --timeout DUR Runtime startup timeout (default: 5m) @@ -621,6 +623,9 @@ Annotations: reports the observed value alongside the expectation whenever the assertion pins its type. @throws 'message' Expect error + @verify NOT IMPLEMENTED — rejected as an error. Nothing + evaluates it, so it would assert nothing. Return + the value from the microflow and use @expect. @cleanup rollback|none What happens to the test's database writes. rollback (the default) wraps the test in a transaction and rolls it back, so nothing it diff --git a/cmd/mxcli/testrunner/assertions_test.go b/cmd/mxcli/testrunner/assertions_test.go new file mode 100644 index 000000000..70176d838 --- /dev/null +++ b/cmd/mxcli/testrunner/assertions_test.go @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: Apache-2.0 + +package testrunner + +import ( + "strings" + "testing" +) + +// TestAssertionCountIsReported is the second half of the silent-pass fix. The +// first half stopped an @expect from being dropped; this one stops a test that +// carries no assertion at all from being indistinguishable, in the output, from +// one that carries six. The reporting suite's 22/22 was 6 real and 16 vacuous, +// and the cheapest way back to green after the first fix is to delete the +// @expect line — which must not look like a repair. +func TestAssertionCountIsReported(t *testing.T) { + cases := []struct { + name string + tc TestCase + want int + }{ + {"two expects", TestCase{Expects: []Expect{expectOf("$a = 1"), expectOf("$b = 2")}}, 2}, + {"throws is an assertion", TestCase{Throws: "boom"}, 1}, + {"nothing at all", TestCase{}, 0}, + // @verify is parsed and never executed, so it asserts nothing and must + // not be counted as if it did. + {"verify does not count", TestCase{Verify: []string{"select 1 = 1"}}, 0}, + } + for _, c := range cases { + if got := c.tc.AssertionCount(); got != c.want { + t.Errorf("%s: AssertionCount() = %d, want %d", c.name, got, c.want) + } + } +} + +// TestZeroAssertionTestsAreVisibleInOutput pins that a smoke test says so. +func TestZeroAssertionTestsAreVisibleInOutput(t *testing.T) { + sr := &SuiteResult{Name: "s", Tests: []TestResult{ + {ID: "1", Name: "asserts things", Status: StatusPass, Assertions: 3}, + {ID: "2", Name: "asserts nothing", Status: StatusPass, Assertions: 0}, + }} + var b strings.Builder + PrintResults(&b, sr, false) + out := b.String() + + if !strings.Contains(out, "3 assertions") { + t.Errorf("assertion count missing from the passing line:\n%s", out) + } + if !strings.Contains(out, "no assertions") { + t.Errorf("a zero-assertion test is not marked:\n%s", out) + } + if !strings.Contains(out, `1 test(s) asserted nothing beyond "did not throw"`) { + t.Errorf("summary does not call out the vacuous tests:\n%s", out) + } +} + +// TestRequireAssertionsMakesVacuousTestsErrors pins the opt-in enforcement. It +// is off by default because a smoke test is legitimate; it exists so a project +// that has decided otherwise can fail its CI on one. +func TestRequireAssertionsMakesVacuousTestsErrors(t *testing.T) { + tc := TestCase{ID: "test_1", Name: "asserts nothing", SourceFile: "a.test.mdl"} + + if res, bad := vacuousResult(tc, false); bad { + t.Errorf("a zero-assertion test errored without --require-assertions: %+v", res) + } + res, bad := vacuousResult(tc, true) + if !bad { + t.Fatal("--require-assertions did not flag a zero-assertion test") + } + if res.Status != StatusError { + t.Errorf("status = %v, want ERROR", res.Status) + } + if !strings.Contains(res.Message, "no assertions") { + t.Errorf("message = %q, want it to say the test asserts nothing", res.Message) + } +} + +// TestVerifyIsRejectedRatherThanIgnored. @verify is documented in the skill's +// annotation table as an OQL post-condition, is parsed into the TestCase, and is +// then read by nothing but `--list`. That is exactly the shape of the defect +// this whole change exists to remove: an annotation that looks like an assertion +// and asserts nothing. Until it is implemented it must be an error. +func TestVerifyIsRejectedRatherThanIgnored(t *testing.T) { + doc := `/** + * @test writes a row + * @verify select count(*) from Mod.E where Code = 'X' = 1 + */` + a := parseAnnotations(doc) + if len(a.AssertionErrors) != 1 { + t.Fatalf("AssertionErrors: got %d, want 1 — @verify was silently ignored", len(a.AssertionErrors)) + } + if !strings.Contains(a.AssertionErrors[0], "@verify") { + t.Errorf("AssertionErrors[0] = %q, want it to name @verify", a.AssertionErrors[0]) + } +} + +// TestJUnitCarriesSourceFileAndAssertions pins the CI-side reporting: a failure +// in a multi-file run has to say which file it came from, and the assertion +// count has to survive into the report a CI actually renders. +func TestJUnitCarriesSourceFileAndAssertions(t *testing.T) { + sr := &SuiteResult{Name: "mxtest", Tests: []TestResult{ + {ID: "1", Name: "a", Status: StatusPass, Assertions: 2, SourceFile: "tests/board.test.mdl"}, + {ID: "2", Name: "b", Status: StatusPass, Assertions: 0, SourceFile: "tests/mix.test.mdl"}, + }} + var b strings.Builder + if err := WriteJUnitXML(&b, sr); err != nil { + t.Fatalf("WriteJUnitXML: %v", err) + } + out := b.String() + if !strings.Contains(out, `classname="tests.board"`) { + t.Errorf("classname does not identify the source file:\n%s", out) + } + if !strings.Contains(out, ``) && + !strings.Contains(out, ``) { + t.Errorf("assertion count missing from the JUnit report:\n%s", out) + } +} diff --git a/cmd/mxcli/testrunner/client.go b/cmd/mxcli/testrunner/client.go index 63880f461..f2f7b3c9d 100644 --- a/cmd/mxcli/testrunner/client.go +++ b/cmd/mxcli/testrunner/client.go @@ -159,11 +159,8 @@ func (c *endpointClient) waitReady(timeout time.Duration) error { // (StatusError — the test did not reach a verdict), or the verdict came back in // a shape this runner does not recognise. func toResult(tc TestCase, rr *runResponse) TestResult { - res := TestResult{ - ID: tc.ID, - Name: tc.Name, - Duration: time.Duration(rr.DurationMicros) * time.Microsecond, - } + res := newResult(tc) + res.Duration = time.Duration(rr.DurationMicros) * time.Microsecond switch { case !rr.OK: res.Status = StatusError diff --git a/cmd/mxcli/testrunner/expect_test.go b/cmd/mxcli/testrunner/expect_test.go index 0601727bd..cebbb8b85 100644 --- a/cmd/mxcli/testrunner/expect_test.go +++ b/cmd/mxcli/testrunner/expect_test.go @@ -184,9 +184,9 @@ func TestExpectActualValueIsTypeSafe(t *testing.T) { } } -// TestParseAnnotationsRecordsExpectErrors pins that a bad @expect survives as an +// TestParseAnnotationsRecordsAssertionErrors pins that a bad @expect survives as an // error on the test rather than vanishing. -func TestParseAnnotationsRecordsExpectErrors(t *testing.T) { +func TestParseAnnotationsRecordsAssertionErrors(t *testing.T) { doc := `/** * @test broken * @expect randomInt($result) = 1 @@ -196,11 +196,11 @@ func TestParseAnnotationsRecordsExpectErrors(t *testing.T) { if len(a.Expects) != 1 { t.Errorf("Expects: got %d, want 1", len(a.Expects)) } - if len(a.ExpectErrors) != 1 { - t.Fatalf("ExpectErrors: got %d, want 1", len(a.ExpectErrors)) + if len(a.AssertionErrors) != 1 { + t.Fatalf("AssertionErrors: got %d, want 1", len(a.AssertionErrors)) } - if !strings.Contains(a.ExpectErrors[0], "randomInt") { - t.Errorf("ExpectErrors[0] = %q, want it to name the function", a.ExpectErrors[0]) + if !strings.Contains(a.AssertionErrors[0], "randomInt") { + t.Errorf("AssertionErrors[0] = %q, want it to name the function", a.AssertionErrors[0]) } } @@ -209,10 +209,10 @@ func TestParseAnnotationsRecordsExpectErrors(t *testing.T) { // FailCount counts, so the run's exit code is non-zero. func TestUncompilableExpectIsAnErrorNotAPass(t *testing.T) { tc := TestCase{ - ID: "test_1", - Name: "broken", - MDL: "$result = CALL MICROFLOW M.Anything();", - ExpectErrors: []string{"@expect randomInt($result) = 1: randomInt() is not a Mendix expression function"}, + ID: "test_1", + Name: "broken", + MDL: "$result = CALL MICROFLOW M.Anything();", + AssertionErrors: []string{"@expect randomInt($result) = 1: randomInt() is not a Mendix expression function"}, } suite := &TestSuite{Name: "s", Tests: []TestCase{tc}} @@ -223,9 +223,9 @@ func TestUncompilableExpectIsAnErrorNotAPass(t *testing.T) { t.Errorf("a test with an uncompilable @expect was generated into the runner:\n%s", mdl) } - res, bad := expectErrorResult(tc) + res, bad := assertionErrorResult(tc) if !bad { - t.Fatal("expectErrorResult did not flag the test") + t.Fatal("assertionErrorResult did not flag the test") } if res.Status != StatusError { t.Errorf("status = %v, want ERROR", res.Status) diff --git a/cmd/mxcli/testrunner/generator.go b/cmd/mxcli/testrunner/generator.go index 29f5f6543..a7ccf081b 100644 --- a/cmd/mxcli/testrunner/generator.go +++ b/cmd/mxcli/testrunner/generator.go @@ -32,7 +32,7 @@ func GenerateTestRunner(suite *TestSuite) string { // A test whose @expect did not compile gets no block. The runner reports // it as an ERROR from the parse message; running it would report a pass // for an assertion that was never made. - if len(tc.ExpectErrors) > 0 { + if len(tc.AssertionErrors) > 0 { continue } writeTestBlock(&b, tc, i) diff --git a/cmd/mxcli/testrunner/generator_endpoint.go b/cmd/mxcli/testrunner/generator_endpoint.go index 3ea8e37c1..e5cf718a9 100644 --- a/cmd/mxcli/testrunner/generator_endpoint.go +++ b/cmd/mxcli/testrunner/generator_endpoint.go @@ -37,7 +37,7 @@ func GenerateTestFlows(suite *TestSuite) string { // A test with an uncompilable @expect gets no microflow. The runner // reports it as an ERROR from the parse message, which is more useful // than a microflow that runs and cannot assert anything. - if len(tc.ExpectErrors) > 0 { + if len(tc.AssertionErrors) > 0 { continue } writeTestFlow(&b, tc) diff --git a/cmd/mxcli/testrunner/junit.go b/cmd/mxcli/testrunner/junit.go index 7695780b5..5b97924e5 100644 --- a/cmd/mxcli/testrunner/junit.go +++ b/cmd/mxcli/testrunner/junit.go @@ -6,6 +6,9 @@ import ( "encoding/xml" "fmt" "io" + "path/filepath" + "strconv" + "strings" "time" ) @@ -28,13 +31,24 @@ type junitTestSuite struct { } type junitTestCase struct { - XMLName xml.Name `xml:"testcase"` - Name string `xml:"name,attr"` - ClassName string `xml:"classname,attr"` - Time string `xml:"time,attr"` - Failure *junitFailure `xml:"failure,omitempty"` - Error *junitError `xml:"error,omitempty"` - Skipped *junitSkipped `xml:"skipped,omitempty"` + XMLName xml.Name `xml:"testcase"` + Name string `xml:"name,attr"` + ClassName string `xml:"classname,attr"` + File string `xml:"file,attr,omitempty"` + Time string `xml:"time,attr"` + Properties *junitProperties `xml:"properties,omitempty"` + Failure *junitFailure `xml:"failure,omitempty"` + Error *junitError `xml:"error,omitempty"` + Skipped *junitSkipped `xml:"skipped,omitempty"` +} + +type junitProperties struct { + Properties []junitProperty `xml:"property"` +} + +type junitProperty struct { + Name string `xml:"name,attr"` + Value string `xml:"value,attr"` } type junitFailure struct { @@ -82,8 +96,15 @@ func convertToJUnitSuite(result *SuiteResult) junitTestSuite { for _, t := range result.Tests { tc := junitTestCase{ Name: t.Name, - ClassName: result.Name, + ClassName: junitClassName(t, result.Name), + File: t.SourceFile, Time: formatDuration(t.Duration), + // The assertion count rides along as a property so a CI report can + // show what a passing test actually checked. Without it, "0 + // assertions" and "6 assertions" are the same green tick. + Properties: &junitProperties{Properties: []junitProperty{ + {Name: "assertions", Value: strconv.Itoa(t.Assertions)}, + }}, } switch t.Status { @@ -112,6 +133,22 @@ func convertToJUnitSuite(result *SuiteResult) junitTestSuite { return suite } +// junitClassName derives a JUnit class name from the test's source file. +// +// Every case used to be stamped with the suite name, so a `tests/` directory +// collapsed into one class and a CI failure could not say which file it came +// from. JUnit consumers treat the class name as a dotted path, so the extension +// is dropped and the separators converted. +func junitClassName(t TestResult, fallback string) string { + if t.SourceFile == "" { + return fallback + } + name := filepath.ToSlash(t.SourceFile) + name = strings.TrimSuffix(name, filepath.Ext(name)) + name = strings.TrimSuffix(name, ".test") + return strings.ReplaceAll(name, "/", ".") +} + func formatDuration(d time.Duration) string { return fmt.Sprintf("%.3f", d.Seconds()) } diff --git a/cmd/mxcli/testrunner/parser.go b/cmd/mxcli/testrunner/parser.go index c00e0074a..3d289f117 100644 --- a/cmd/mxcli/testrunner/parser.go +++ b/cmd/mxcli/testrunner/parser.go @@ -19,17 +19,30 @@ type TestCase struct { Name string // From @test annotation MDL string // Raw MDL statements for this test block Expects []Expect // @expect assertions - // ExpectErrors holds one message per @expect the runner could not compile - // into an assertion. A test carrying any of these is reported as an ERROR + // AssertionErrors holds one message per annotation that claims to assert + // something and cannot. A test carrying any of these is reported as an ERROR // and never run: an assertion that cannot be evaluated must not be able to // report a pass. - ExpectErrors []string - Verify []string // @verify OQL queries - Setup string // @setup block reference - Cleanup string // @cleanup strategy ("rollback" or "none") - Throws string // @throws expected error message - SourceFile string // Original file path - Line int // Line number in source file + AssertionErrors []string + Verify []string // @verify OQL queries + Setup string // @setup block reference + Cleanup string // @cleanup strategy ("rollback" or "none") + Throws string // @throws expected error message + SourceFile string // Original file path + Line int // Line number in source file +} + +// AssertionCount reports how many assertions the test actually makes. +// +// @expect and @throws each assert something a runner evaluates. @verify does +// not — it is parsed and never executed, which is why it is rejected at parse +// time rather than counted here. +func (tc TestCase) AssertionCount() int { + n := len(tc.Expects) + if tc.Throws != "" { + n++ + } + return n } // TestSuite represents a collection of tests from one or more files. @@ -138,17 +151,17 @@ func parseMDLTests(content string, sourcePath string) ([]TestCase, error) { testID := fmt.Sprintf("test_%d", i+1) tests = append(tests, TestCase{ - ID: testID, - Name: annotations.Test, - MDL: strings.TrimSpace(body), - Expects: annotations.Expects, - ExpectErrors: annotations.ExpectErrors, - Verify: annotations.Verify, - Setup: annotations.Setup, - Cleanup: annotations.Cleanup, - Throws: annotations.Throws, - SourceFile: sourcePath, - Line: line, + ID: testID, + Name: annotations.Test, + MDL: strings.TrimSpace(body), + Expects: annotations.Expects, + AssertionErrors: annotations.AssertionErrors, + Verify: annotations.Verify, + Setup: annotations.Setup, + Cleanup: annotations.Cleanup, + Throws: annotations.Throws, + SourceFile: sourcePath, + Line: line, }) } @@ -201,17 +214,17 @@ func parseMarkdownTests(content string, sourcePath string) ([]TestCase, error) { } tests = append(tests, TestCase{ - ID: testID, - Name: name, - MDL: strings.TrimSpace(body), - Expects: annotations.Expects, - ExpectErrors: annotations.ExpectErrors, - Verify: annotations.Verify, - Setup: annotations.Setup, - Cleanup: annotations.Cleanup, - Throws: annotations.Throws, - SourceFile: sourcePath, - Line: blockStart, + ID: testID, + Name: name, + MDL: strings.TrimSpace(body), + Expects: annotations.Expects, + AssertionErrors: annotations.AssertionErrors, + Verify: annotations.Verify, + Setup: annotations.Setup, + Cleanup: annotations.Cleanup, + Throws: annotations.Throws, + SourceFile: sourcePath, + Line: blockStart, }) } else { blockLines = append(blockLines, line) @@ -279,13 +292,13 @@ func extractDocAndBody(block string, fullContent string) (string, string, int) { // annotations holds parsed javadoc annotations for a test block. type annotations struct { - Test string - Expects []Expect - ExpectErrors []string - Verify []string - Setup string - Cleanup string - Throws string + Test string + Expects []Expect + AssertionErrors []string + Verify []string + Setup string + Cleanup string + Throws string } var ( @@ -326,13 +339,23 @@ func parseAnnotations(doc string) annotations { if m := expectPattern.FindStringSubmatch(line); m != nil { exp, err := ParseExpect(m[1]) if err != nil { - a.ExpectErrors = append(a.ExpectErrors, err.Error()) + a.AssertionErrors = append(a.AssertionErrors, err.Error()) } else { a.Expects = append(a.Expects, exp) } } if m := verifyPattern.FindStringSubmatch(line); m != nil { + // @verify is parsed here, listed by --list, and read by nothing + // else — no runner has ever executed one. A documented annotation + // that looks like an assertion and asserts nothing is the same + // defect as the @expect shapes that used to be dropped, so it gets + // the same answer: an error, not silence. When it is implemented, + // this branch becomes the OQL post-condition it claims to be. a.Verify = append(a.Verify, strings.TrimSpace(m[1])) + a.AssertionErrors = append(a.AssertionErrors, fmt.Sprintf( + "@verify %s: @verify is not implemented — no runner evaluates it, "+ + "so it would assert nothing. Assert on the microflow's own "+ + "result with @expect instead", strings.TrimSpace(m[1]))) } if m := setupPattern.FindStringSubmatch(line); m != nil { a.Setup = strings.TrimSpace(m[1]) diff --git a/cmd/mxcli/testrunner/results.go b/cmd/mxcli/testrunner/results.go index 885010d80..7364ebe75 100644 --- a/cmd/mxcli/testrunner/results.go +++ b/cmd/mxcli/testrunner/results.go @@ -17,6 +17,26 @@ type TestResult struct { Status TestStatus // Pass, Fail, Skip, Error Message string // Failure/skip message Duration time.Duration // Execution time + // Assertions is how many assertions the test made. A PASS with zero of them + // says only that the body did not throw, and the output has to make that + // visible — a suite whose green cannot be told apart from a vacuous one is + // the failure this whole area exists to prevent. + Assertions int + // SourceFile is the test file the case came from, so a multi-file run's + // report can say where a failure lives. + SourceFile string +} + +// newResult starts a result from its test case, carrying across everything the +// case already knows. Every construction site goes through this, so a new field +// on TestResult cannot be populated in one path and silently missing in another. +func newResult(tc TestCase) TestResult { + return TestResult{ + ID: tc.ID, + Name: tc.Name, + Assertions: tc.AssertionCount(), + SourceFile: tc.SourceFile, + } } // TestStatus represents the outcome status of a test. @@ -74,6 +94,42 @@ func (sr *SuiteResult) FailCount() int { return n } +// resultNote renders the parenthetical after a test's name: how long it took and +// how much it actually asserted. +// +// The assertion count is here rather than buried in a --verbose mode because the +// whole lesson of the silent-pass defect is that a suite's ordinary output has to +// distinguish a test that asserted from one that did not. +func resultNote(t TestResult) string { + var parts []string + if t.Duration > 0 { + parts = append(parts, t.Duration.Round(time.Millisecond).String()) + } + switch { + case t.Status == StatusSkip || t.Status == StatusError: + // Neither reached its assertions, so a count would say nothing. + case t.Assertions == 0: + parts = append(parts, "no assertions") + case t.Assertions == 1: + parts = append(parts, "1 assertion") + default: + parts = append(parts, fmt.Sprintf("%d assertions", t.Assertions)) + } + return strings.Join(parts, ", ") +} + +// VacuousCount returns the number of tests that reached a verdict without +// asserting anything — a pass that means only "the body did not throw". +func (sr *SuiteResult) VacuousCount() int { + n := 0 + for _, t := range sr.Tests { + if t.Assertions == 0 && (t.Status == StatusPass || t.Status == StatusFail) { + n++ + } + } + return n +} + // ErrorCount returns the number of tests that did not reach a verdict — an // uncompilable @expect, a missing microflow, a failed request. // @@ -229,24 +285,28 @@ func ParseLogResults(logReader io.Reader, suite *TestSuite) *SuiteResult { // A test whose @expect did not compile was never generated, so the log // has nothing to say about it. Report the parse error rather than the // generic "not executed". - if res, bad := expectErrorResult(tc); bad { + if res, bad := assertionErrorResult(tc); bad { result.Tests = append(result.Tests, res) continue } if r, ok := resultMap[tc.ID]; ok { - // Use the original test name if available - if tc.Name != "" { - r.Name = tc.Name + // The log carries a status and a duration; everything else is known + // from the test case and is stamped on here so this path reports the + // same fields as the endpoint path. + res := newResult(tc) + if tc.Name == "" { + res.Name = r.Name } - result.Tests = append(result.Tests, *r) + res.Status = r.Status + res.Message = r.Message + res.Duration = r.Duration + result.Tests = append(result.Tests, res) } else { // Test was not executed — mark as error - result.Tests = append(result.Tests, TestResult{ - ID: tc.ID, - Name: tc.Name, - Status: StatusError, - Message: "Test was not executed (runtime may have crashed before reaching it)", - }) + res := newResult(tc) + res.Status = StatusError + res.Message = "Test was not executed (runtime may have crashed before reaching it)" + result.Tests = append(result.Tests, res) } } @@ -277,8 +337,8 @@ func PrintResults(w io.Writer, result *SuiteResult, color bool) { } fmt.Fprintf(w, " %s %s", statusStr, t.Name) - if t.Duration > 0 { - fmt.Fprintf(w, " (%s)", t.Duration.Round(time.Millisecond)) + if note := resultNote(t); note != "" { + fmt.Fprintf(w, " (%s)", note) } fmt.Fprintln(w) @@ -287,6 +347,15 @@ func PrintResults(w io.Writer, result *SuiteResult, color bool) { } } + // A vacuous test is not a failure, but it must never be silent: after the + // @expect fix the cheapest way back to a green suite is to delete the + // assertion, and that must not look like a repair. + if n := result.VacuousCount(); n > 0 { + fmt.Fprintf(w, "%s\n", strings.Repeat("-", 60)) + fmt.Fprintf(w, "%d test(s) asserted nothing beyond \"did not throw\". "+ + "Run with --require-assertions to make that an error.\n", n) + } + fmt.Fprintf(w, "%s\n", strings.Repeat("-", 60)) errors := result.ErrorCount() fmt.Fprintf(w, "Total: %d Passed: %d Failed: %d", @@ -315,23 +384,40 @@ func PrintResults(w io.Writer, result *SuiteResult, color bool) { } } -// expectErrorResult turns a test whose @expect could not be compiled into an -// ERROR result. +// assertionErrorResult turns a test whose assertions could not be compiled into +// an ERROR result. // -// This is the fail-closed rule the whole @expect pipeline is built around: an +// This is the fail-closed rule the whole annotation pipeline is built around: an // assertion the runner cannot evaluate must never be able to report a pass. The -// previous implementation dropped such a line during parsing, and a test with no +// original implementation dropped such a line during parsing, and a test with no // assertions left passes as long as it does not throw — so a suite could report // green while asserting nothing. ERROR is counted with the failures, so the run's // exit code is non-zero. -func expectErrorResult(tc TestCase) (TestResult, bool) { - if len(tc.ExpectErrors) == 0 { +func assertionErrorResult(tc TestCase) (TestResult, bool) { + if len(tc.AssertionErrors) == 0 { return TestResult{}, false } - return TestResult{ - ID: tc.ID, - Name: tc.Name, - Status: StatusError, - Message: strings.Join(tc.ExpectErrors, "; "), - }, true + res := newResult(tc) + res.Status = StatusError + res.Message = strings.Join(tc.AssertionErrors, "; ") + return res, true +} + +// vacuousResult turns a test that asserts nothing into an ERROR, but only when +// the run asked for that. +// +// It is opt-in because a smoke test — "this microflow runs without throwing" — +// is a legitimate thing to write, and is documented as such. What is not +// legitimate is a suite in which nobody can tell the two apart, and the summary +// line handles that unconditionally. This flag is for a project that has decided +// every test must assert. +func vacuousResult(tc TestCase, require bool) (TestResult, bool) { + if !require || tc.AssertionCount() > 0 { + return TestResult{}, false + } + res := newResult(tc) + res.Status = StatusError + res.Message = "the test has no assertions — it can only report that the body did not throw " + + "(add an @expect, or drop --require-assertions)" + return res, true } diff --git a/cmd/mxcli/testrunner/runner.go b/cmd/mxcli/testrunner/runner.go index c9b3ef04a..5c27ce536 100644 --- a/cmd/mxcli/testrunner/runner.go +++ b/cmd/mxcli/testrunner/runner.go @@ -84,6 +84,13 @@ type RunOptions struct { // JUnitOutput is the path for JUnit XML output (empty = no file output). JUnitOutput string + // RequireAssertions turns a test that asserts nothing into an ERROR. + // + // Off by default: a smoke test — "this microflow runs without throwing" — is + // a legitimate thing to write. The summary line reports vacuous tests either + // way; this is for a project that has decided every test must assert. + RequireAssertions bool + // Verbose shows all runtime log output. Verbose bool @@ -378,14 +385,14 @@ func ListTests(files []string, w io.Writer) error { for _, exp := range tc.Expects { fmt.Fprintf(w, " @expect %s\n", exp.Raw) } - for _, e := range tc.ExpectErrors { - fmt.Fprintf(w, " ERROR: %s\n", e) - } if tc.Throws != "" { fmt.Fprintf(w, " @throws '%s'\n", tc.Throws) } - for _, v := range tc.Verify { - fmt.Fprintf(w, " @verify %s\n", v) + for _, e := range tc.AssertionErrors { + fmt.Fprintf(w, " ERROR: %s\n", e) + } + if tc.AssertionCount() == 0 && len(tc.AssertionErrors) == 0 { + fmt.Fprintf(w, " (no assertions — this test can only report that the body did not throw)\n") } } return nil diff --git a/cmd/mxcli/testrunner/runner_endpoint.go b/cmd/mxcli/testrunner/runner_endpoint.go index 17b79046f..419e4d59a 100644 --- a/cmd/mxcli/testrunner/runner_endpoint.go +++ b/cmd/mxcli/testrunner/runner_endpoint.go @@ -120,19 +120,21 @@ func runSuite(client *endpointClient, suite *TestSuite, opts RunOptions, w io.Wr leaked := 0 for _, tc := range suite.Tests { - if res, bad := expectErrorResult(tc); bad { + if res, bad := assertionErrorResult(tc); bad { + result.Tests = append(result.Tests, res) + continue + } + if res, bad := vacuousResult(tc, opts.RequireAssertions); bad { result.Tests = append(result.Tests, res) continue } flow := testFlowName(tc) if !present[flow] { - result.Tests = append(result.Tests, TestResult{ - ID: tc.ID, - Name: tc.Name, - Status: StatusError, - Message: fmt.Sprintf("microflow %s was not created — the test body may not have compiled", flow), - }) + res := newResult(tc) + res.Status = StatusError + res.Message = fmt.Sprintf("microflow %s was not created — the test body may not have compiled", flow) + result.Tests = append(result.Tests, res) continue } @@ -141,12 +143,10 @@ func runSuite(client *endpointClient, suite *TestSuite, opts RunOptions, w io.Wr if err != nil { // A transport failure is not a verdict. Report it against this test // and keep going; if the runtime died the rest will say so too. - result.Tests = append(result.Tests, TestResult{ - ID: tc.ID, - Name: tc.Name, - Status: StatusError, - Message: fmt.Sprintf("calling the test endpoint: %v", err), - }) + res := newResult(tc) + res.Status = StatusError + res.Message = fmt.Sprintf("calling the test endpoint: %v", err) + result.Tests = append(result.Tests, res) continue } diff --git a/docs-site/src/tools/running-tests.md b/docs-site/src/tools/running-tests.md index 39922074a..70048bfcc 100644 --- a/docs-site/src/tools/running-tests.md +++ b/docs-site/src/tools/running-tests.md @@ -162,6 +162,36 @@ pins down its type (`@expect $a = $b`, where both sides are variables). Mendix's expression engine is typed, and a wrong guess would fail the build rather than the test. +### A test that asserts nothing, and `@verify` + +Every result line reports what the test actually checked: + +``` + PASS the board is 81 squares (6ms, 2 assertions) + FAIL the mix keeps the shared block (8ms, 1 assertion) + expected length($result) = 81, actual: 27 + PASS asserts nothing at all (4ms, no assertions) +------------------------------------------------------------ +1 test(s) asserted nothing beyond "did not throw". Run with +--require-assertions to make that an error. +``` + +A test with no `@expect` and no `@throws` is a smoke test — it reports only that +the body did not throw. It still passes, because that is a legitimate thing to +write; what it may not do is look the same as a test with six assertions. +`--require-assertions` makes every vacuous test an ERROR for projects that want +CI to enforce it. + +`@verify` was documented as an OQL post-condition and **is not implemented** — +it is parsed and evaluated by nothing, so a test whose only assertion was a +`@verify` asserted nothing. It is now rejected with an error pointing at +`@expect`. To check a database post-condition, return the value from the +microflow under test and assert on it, or query the app with `mxcli oql`. + +The JUnit report (`--junit`) carries the assertion count as a +`` on each case, and `classname`/`file` identify the +source test file so a failure in a multi-file run says where it lives. + ### The app's own after-startup microflow Boot registers the endpoint and then runs the project's own after-startup diff --git a/mdl-examples/bug-tests/expect-vacuous-assertions.test.mdl b/mdl-examples/bug-tests/expect-vacuous-assertions.test.mdl new file mode 100644 index 000000000..ccb0689b9 --- /dev/null +++ b/mdl-examples/bug-tests/expect-vacuous-assertions.test.mdl @@ -0,0 +1,59 @@ +-- FINDINGS #46 follow-up: a test that asserts nothing must say so. +-- +-- The first fix stopped an @expect from being silently dropped. This file is the +-- residual case: a test can still assert nothing by simply not carrying an +-- @expect, and before this change its PASS line was indistinguishable from a +-- test with six real assertions. After the @expect fix, deleting the assertion +-- is the cheapest way back to a green suite — which must not read as a repair. +-- +-- No project is needed to see it: +-- +-- mxcli test mdl-examples/bug-tests/expect-vacuous-assertions.test.mdl --list +-- +-- Expected output — three tests, three different things to say about them: +-- +-- test_1: asserts two things +-- @expect length($result) = 81 +-- @expect find($result, '0') >= 0 +-- test_2: asserts nothing at all +-- (no assertions — this test can only report that the body did not throw) +-- test_3: believes it asserts something +-- ERROR: @verify ...: @verify is not implemented — no runner evaluates it, +-- so it would assert nothing. ... +-- +-- A run reports the same three states, and summarises the vacuous ones: +-- +-- PASS asserts two things (6ms, 2 assertions) +-- PASS asserts nothing at all (4ms, no assertions) +-- ERROR believes it asserts something +-- 1 test(s) asserted nothing beyond "did not throw". Run with +-- --require-assertions to make that an error. +-- +-- test_2 still passes by default: a smoke test is a legitimate thing to write. +-- --require-assertions turns it into an ERROR for a project that has decided +-- otherwise. test_3 is an error either way — @verify was documented as an OQL +-- post-condition and is evaluated by nothing, which is the same defect as a +-- dropped @expect wearing a different annotation. +-- +-- The microflows referenced below do not need to exist for --list. + +/** + * @test asserts two things + * @expect length($result) = 81 + * @expect find($result, '0') >= 0 + */ +$result = CALL MICROFLOW MyModule.Deal(); +/ + +/** + * @test asserts nothing at all + */ +$result = CALL MICROFLOW MyModule.Deal(); +/ + +/** + * @test believes it asserts something + * @verify select count(*) from MyModule.Board where Code = 'X' = 1 + */ +$result = CALL MICROFLOW MyModule.Deal(); +/ From 2b3a60f1e2a842fb35992df867485f1e84171a46 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 17:55:11 +0000 Subject: [PATCH 10/22] Ship the Vega pack's lockfile, so the command it documents can run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `references/install.md` and SKILL.md both say `npm ci`, and the pack shipped no package-lock.json, so the one command it told people to run failed on the spot: npm error The `npm ci` command can only install with an existing package-lock.json Found by the formula1 project, which hit it doing exactly what the pack said. The reason it shipped is worth recording. The end-to-end verification did build the widget and produce a correctly-namespaced .mpk — using `npm install`. So the build was proven while the documented path was never exercised. A verification that quietly substitutes a working command for the published one proves the wrong thing. Shipping the lock rather than downgrading to `npm install`, because the pack's value is a build that still works later. The three direct dependencies are pinned exactly, but their transitive tree is not, so without a lock the build drifts — surfacing as a compile error in somebody else's project, months on, from a package nobody chose to upgrade. 974 KB of JSON, and about 1% on the binary. Substitution cannot desync it: the tokens live in `packagePath` and `config.projectPath`, while npm's lockfile records only name, version, license, dependencies and devDependencies for the root package. Verified rather than reasoned about — installed the pack with `--namespace acme`, then ran the documented `npm ci && npm run build` against the SUBSTITUTED tree: 1512 packages, and every path inside the built .mpk under acme/widget/web/vegachart with a matching id in VegaChart.xml. Two guards so the class does not recur: - TestWidgetPacksShipALockfile — a pack shipping widget/package.json must ship the lock beside it. Anchored on package.json rather than on the prose, since a pack that builds JavaScript wants a reproducible tree whatever its docs say. Confirmed against the pack as it shipped in main: it fails with the exact complaint. - TestLockfilesAreNotRewritten — a lock must never be listed under rewrite.files. It records resolved integrity hashes, so substituting into one invalidates them and `npm ci` fails on a checksum, which reads as a corrupt registry rather than a packaging mistake. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4 --- .../mendix-vega-charts/references/install.md | 12 + .../widget/package-lock.json | 22825 ++++++++++++++++ cmd/mxcli/skillpacks_test.go | 63 + 3 files changed, 22900 insertions(+) create mode 100644 .claude/skills/packs/mendix-vega-charts/widget/package-lock.json diff --git a/.claude/skills/packs/mendix-vega-charts/references/install.md b/.claude/skills/packs/mendix-vega-charts/references/install.md index ee686a2bf..35213270d 100644 --- a/.claude/skills/packs/mendix-vega-charts/references/install.md +++ b/.claude/skills/packs/mendix-vega-charts/references/install.md @@ -54,6 +54,18 @@ build's `projectPath` relative to where the source went, so there is nothing to copy. Verified end to end on a Mendix 11.12.1 app: every path inside the built package is under the new namespace, and so is the id in `VegaChart.xml`. +`npm ci`, not `npm install`, and the pack ships the `package-lock.json` that +makes it work. The three direct dependencies are pinned exactly, but their +transitive tree is not, so without a lock the build drifts — and the way that +surfaces is a compile error in somebody else's project, months later, from a +package nobody chose to upgrade. + +Substitution cannot desync the lock: the tokens live in `packagePath` and +`config.projectPath`, and npm's lockfile records only `name`, `version`, +`license`, `dependencies` and `devDependencies` for the root package. `npm ci` +is run against the *substituted* tree in CI-equivalent conditions before each +release of this pack, not against the pristine one. + ## 3a. Let mxcli discover it ```bash diff --git a/.claude/skills/packs/mendix-vega-charts/widget/package-lock.json b/.claude/skills/packs/mendix-vega-charts/widget/package-lock.json new file mode 100644 index 000000000..40b6bce34 --- /dev/null +++ b/.claude/skills/packs/mendix-vega-charts/widget/package-lock.json @@ -0,0 +1,22825 @@ +{ + "name": "vegachart", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vegachart", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "vega": "6.3.1", + "vega-embed": "7.1.0", + "vega-lite": "6.4.3" + }, + "devDependencies": { + "@mendix/pluggable-widgets-tools": "11.12.1", + "@types/big.js": "^6.2.2" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz", + "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.29.7.tgz", + "integrity": "sha512-zxt+UJTOMKvUt3yOg+D58MLuz334pHp93qifMFcjIIO+9hN6t+ufw2gi7vDPMpxvfnHRR+3VVXvIjineCcgyXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0", + "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/@babel/eslint-parser/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.29.7.tgz", + "integrity": "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.29.7.tgz", + "integrity": "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-wrap-function": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.29.7.tgz", + "integrity": "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.29.7.tgz", + "integrity": "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.29.7.tgz", + "integrity": "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.29.7.tgz", + "integrity": "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.7.tgz", + "integrity": "sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.29.7.tgz", + "integrity": "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.29.7.tgz", + "integrity": "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-export-default-from": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.29.7.tgz", + "integrity": "sha512-p+G5BNXDcy3bOXplhY4HybQ1GxH3i2Tppmdm/3epyRu2VgJJZuUlZ61MqRTg582Q7ZLBdP7fePYvsumSEkMxcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-default-from": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.29.7.tgz", + "integrity": "sha512-foag0BB37ROhdeIX9O8G0jX7hw0UekJc04cHMrYLOnrErsnBKqJGHJ8eDRpoCFZBvEPPygmmtw4qyU97qa4oOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.29.7.tgz", + "integrity": "sha512-ajMX6QPcyomotqwpzhkYGxcK2i/us0rs1Qo9QvUpa+Fca0FTmqrzKrctoIYLMxcOhGZldGT/BAVkRGTWBiR8gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.29.7.tgz", + "integrity": "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", + "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz", + "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz", + "integrity": "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.29.7.tgz", + "integrity": "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.29.7.tgz", + "integrity": "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.29.7.tgz", + "integrity": "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz", + "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.29.7.tgz", + "integrity": "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.29.7.tgz", + "integrity": "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.29.7.tgz", + "integrity": "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/template": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz", + "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.29.7.tgz", + "integrity": "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.29.7.tgz", + "integrity": "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.29.7.tgz", + "integrity": "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.29.7.tgz", + "integrity": "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.29.7.tgz", + "integrity": "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.29.7.tgz", + "integrity": "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.29.7.tgz", + "integrity": "sha512-wRHeUjUjCZnMHmiO5bRgjFLcoEh7JyTdByOW11ahhwNa4V0bmeGEaIvt51yq0zQp2yWIpqfxXXPyUP6GFJZHOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-syntax-flow": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.29.7.tgz", + "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.29.7.tgz", + "integrity": "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.29.7.tgz", + "integrity": "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.29.7.tgz", + "integrity": "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.29.7.tgz", + "integrity": "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.29.7.tgz", + "integrity": "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.29.7.tgz", + "integrity": "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.8.tgz", + "integrity": "sha512-6iSnEK0zlkLKU4heofK/AdmRD4e2SHVpJMtrwnTCzhnaM98ria4rTrOXBBi45BTTYnJtO8txnPsX4fChYXkmeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.29.7.tgz", + "integrity": "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.29.7.tgz", + "integrity": "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz", + "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.29.7.tgz", + "integrity": "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.29.7.tgz", + "integrity": "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.29.7.tgz", + "integrity": "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.29.7.tgz", + "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz", + "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.29.7.tgz", + "integrity": "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.29.7.tgz", + "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.29.7.tgz", + "integrity": "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.29.7.tgz", + "integrity": "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.29.7.tgz", + "integrity": "sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.29.7.tgz", + "integrity": "sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.29.7.tgz", + "integrity": "sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.29.7.tgz", + "integrity": "sha512-H5E+HBgDpr6Q5t+Aj11tL7XkIui1jhbIoArVQnqjgXo5/3YxkN7ZEBcWF4RQlB0T4rrxJQbXS6kiFV6B7XTqUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.8.tgz", + "integrity": "sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.29.7.tgz", + "integrity": "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.29.7.tgz", + "integrity": "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.7.tgz", + "integrity": "sha512-xmAscdE/AsqRW7vutbPNoUmu/nF5SrLKPs7aoJgEjo35lLKA/Bc0i2rMv/hr1+Y0o1bQCiVtith3u2vdgRL39Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz", + "integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.29.8.tgz", + "integrity": "sha512-4S9ksMGVWUshvgK0mKfvZky7leuG5/uoFVwMpAomJ8bMoDJiNHRVmc1EglwW/CmGVSqqWpEbXm9FmbRit22qoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.29.7.tgz", + "integrity": "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.29.7.tgz", + "integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.29.7.tgz", + "integrity": "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", + "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.29.7.tgz", + "integrity": "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.29.7.tgz", + "integrity": "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.29.7.tgz", + "integrity": "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.29.7.tgz", + "integrity": "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.7.tgz", + "integrity": "sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.29.7", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.29.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.29.7", + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.29.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.29.7", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.29.7", + "@babel/plugin-syntax-import-attributes": "^7.29.7", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.29.7", + "@babel/plugin-transform-async-generator-functions": "^7.29.7", + "@babel/plugin-transform-async-to-generator": "^7.29.7", + "@babel/plugin-transform-block-scoped-functions": "^7.29.7", + "@babel/plugin-transform-block-scoping": "^7.29.7", + "@babel/plugin-transform-class-properties": "^7.29.7", + "@babel/plugin-transform-class-static-block": "^7.29.7", + "@babel/plugin-transform-classes": "^7.29.7", + "@babel/plugin-transform-computed-properties": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-dotall-regex": "^7.29.7", + "@babel/plugin-transform-duplicate-keys": "^7.29.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.7", + "@babel/plugin-transform-dynamic-import": "^7.29.7", + "@babel/plugin-transform-explicit-resource-management": "^7.29.7", + "@babel/plugin-transform-exponentiation-operator": "^7.29.7", + "@babel/plugin-transform-export-namespace-from": "^7.29.7", + "@babel/plugin-transform-for-of": "^7.29.7", + "@babel/plugin-transform-function-name": "^7.29.7", + "@babel/plugin-transform-json-strings": "^7.29.7", + "@babel/plugin-transform-literals": "^7.29.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.29.7", + "@babel/plugin-transform-member-expression-literals": "^7.29.7", + "@babel/plugin-transform-modules-amd": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-modules-systemjs": "^7.29.7", + "@babel/plugin-transform-modules-umd": "^7.29.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.7", + "@babel/plugin-transform-new-target": "^7.29.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.29.7", + "@babel/plugin-transform-numeric-separator": "^7.29.7", + "@babel/plugin-transform-object-rest-spread": "^7.29.7", + "@babel/plugin-transform-object-super": "^7.29.7", + "@babel/plugin-transform-optional-catch-binding": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/plugin-transform-private-methods": "^7.29.7", + "@babel/plugin-transform-private-property-in-object": "^7.29.7", + "@babel/plugin-transform-property-literals": "^7.29.7", + "@babel/plugin-transform-regenerator": "^7.29.7", + "@babel/plugin-transform-regexp-modifiers": "^7.29.7", + "@babel/plugin-transform-reserved-words": "^7.29.7", + "@babel/plugin-transform-shorthand-properties": "^7.29.7", + "@babel/plugin-transform-spread": "^7.29.7", + "@babel/plugin-transform-sticky-regex": "^7.29.7", + "@babel/plugin-transform-template-literals": "^7.29.7", + "@babel/plugin-transform-typeof-symbol": "^7.29.7", + "@babel/plugin-transform-unicode-escapes": "^7.29.7", + "@babel/plugin-transform-unicode-property-regex": "^7.29.7", + "@babel/plugin-transform-unicode-regex": "^7.29.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.29.7", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-flow": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.29.7.tgz", + "integrity": "sha512-KYIRV0BuaN68CDdsqFkAD7MU7yipUqQNuNElwATdxaIdpTjhvtY82QvkBJs7zV3Evxj2jFAAZ1iO8nyy0nhjqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-transform-flow-strip-types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.29.7.tgz", + "integrity": "sha512-C+PV1TFUPTmBQGoPBL8j2QmLpZ117YTCwxIZeJOM96GbYMFSc7/pOXU5lVykwnZxyTqQxRsvoRk6f2FktZgGHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-transform-react-display-name": "^7.29.7", + "@babel/plugin-transform-react-jsx": "^7.29.7", + "@babel/plugin-transform-react-jsx-development": "^7.29.7", + "@babel/plugin-transform-react-pure-annotations": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz", + "integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/register": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.29.7.tgz", + "integrity": "sha512-AMGJoWuES861riy6pcB0fphE1YXybtQnBYQMuIyPv6mKLiosfa79BKTnAOyx215c/3RJPJpdQwoHZ3earVH7AA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.6", + "source-map-support": "^0.5.16" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/register/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@babel/register/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/register/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.3.0", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "dev": true, + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz", + "integrity": "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/console/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/console/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/console/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/console/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/jest-message-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.3.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/console/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/console/node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/console/node_modules/pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/console/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/core": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz", + "integrity": "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.3.0", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.3.0", + "jest-config": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-resolve-dependencies": "30.3.0", + "jest-runner": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "jest-watcher": "30.3.0", + "pretty-format": "30.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core/node_modules/@jest/transform": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", + "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.3.0", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", + "chalk": "^4.1.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/babel-plugin-istanbul": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/core/node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/core/node_modules/jest-haste-map": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", + "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "picomatch": "^4.0.3", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/@jest/core/node_modules/jest-message-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.3.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core/node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core/node_modules/pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/core/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/core/node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@jest/create-cache-key-function": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-30.4.1.tgz", + "integrity": "sha512-R+xGEtzA95NIsvpXJSROG4t01956dDOt17KpamguY4XOnGvdHNFFXE7Er0C1OAsRjOwiIxpKqOvGlznIGZIQlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/diff-sequences": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", + "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz", + "integrity": "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-mock": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.3.0.tgz", + "integrity": "sha512-0hNFs5N6We3DMCwobzI0ydhkY10sT1tZSC0AAiy+0g2Dt/qEWgrcV5BrMxPczhe41cxW4qm6X+jqZaUdpZIajA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", + "@types/jsdom": "^21.1.7", + "@types/node": "*", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/jest-mock": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", + "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment/node_modules/jest-mock": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", + "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment/node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "30.3.0", + "jest-snapshot": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz", + "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect/node_modules/@jest/diff-sequences": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", + "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect/node_modules/@jest/expect-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz", + "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/expect/node_modules/expect": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.3.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect/node_modules/jest-diff": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", + "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.3.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect/node_modules/jest-matcher-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", + "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.3.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect/node_modules/jest-message-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.3.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect/node_modules/jest-mock": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", + "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect/node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect/node_modules/pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/fake-timers": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz", + "integrity": "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@sinonjs/fake-timers": "^15.0.0", + "@types/node": "*", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/fake-timers/node_modules/jest-message-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.3.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/jest-mock": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", + "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz", + "integrity": "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/types": "30.3.0", + "jest-mock": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals/node_modules/jest-mock": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", + "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals/node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", + "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz", + "integrity": "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@jridgewell/trace-mapping": "^0.3.25", + "@types/node": "*", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^5.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "slash": "^3.0.0", + "string-length": "^4.0.2", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/@jest/transform": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", + "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.3.0", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", + "chalk": "^4.1.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/babel-plugin-istanbul": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/reporters/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/reporters/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "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", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/reporters/node_modules/jest-haste-map": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", + "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "picomatch": "^4.0.3", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/@jest/reporters/node_modules/jest-message-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.3.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/node_modules/pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/reporters/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz", + "integrity": "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz", + "integrity": "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.3.0", + "@jest/types": "30.3.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz", + "integrity": "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.3.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-sequencer/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-sequencer/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-sequencer/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-sequencer/node_modules/jest-haste-map": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", + "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "picomatch": "^4.0.3", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/@jest/test-sequencer/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-sequencer/node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/node_modules/@sinclair/typebox": { + "version": "0.27.12", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.12.tgz", + "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/transform/node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", + "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.4.0", + "@jest/schemas": "30.4.1", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mendix/pluggable-widgets-tools": { + "version": "11.12.1", + "resolved": "https://registry.npmjs.org/@mendix/pluggable-widgets-tools/-/pluggable-widgets-tools-11.12.1.tgz", + "integrity": "sha512-wSg1NsaIlUEzO8KrnKKRymUx3mb898++tsLgByBXnl0XhsNeYWtG+zNGZzjEjLTVc2Kou9C9raxh8fkxn9Njpw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/eslint-parser": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-react-jsx": "^7.28.6", + "@babel/preset-env": "^7.29.2", + "@babel/preset-react": "^7.28.5", + "@prettier/plugin-xml": "^1.2.0", + "@react-native/babel-preset": "0.77.3", + "@rollup/plugin-alias": "^5.1.1", + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-commonjs": "^29.0.2", + "@rollup/plugin-image": "^3.0.3", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^15.3.0", + "@rollup/plugin-terser": "^1.0.0", + "@rollup/plugin-typescript": "^12.1.1", + "@rollup/plugin-url": "^8.0.2", + "@rollup/pluginutils": "^5.3.0", + "@swc/core": "^1.10.0", + "@swc/jest": "^0.2.37", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/react-native": "^13.3.3", + "@testing-library/user-event": "^14.6.1", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@types/semver": "^7.7.1", + "@types/testing-library__jest-dom": "^5.14.5", + "@typescript-eslint/eslint-plugin": "^8.59.0", + "@typescript-eslint/parser": "^8.59.0", + "ansi-colors": "4.1.3", + "babel-jest": "^29.0.0", + "big.js": "^6.0.2", + "core-js": "^3.6.5", + "dotenv": "^17.4.2", + "eslint": "^9.39.4", + "eslint-config-prettier": "^8.0.0", + "eslint-plugin-jest": "^29.15.2", + "eslint-plugin-prettier": "^3.3.1", + "eslint-plugin-promise": "^4.3.1", + "eslint-plugin-react": "~7.37.5", + "eslint-plugin-react-hooks": "^7.1.1", + "fast-glob": "^3.0.0", + "fs-extra": "^11.3.4", + "identity-obj-proxy": "^3.0.0", + "jasmine": "^3.6.2", + "jasmine-core": "^3.6.0", + "jest": "~30.3.0", + "jest-environment-jsdom": "~30.3.0", + "jest-jasmine2": "~30.3.0", + "jest-junit": "^17.0.0", + "make-dir": "^5.1.0", + "mendix": "^11.8.0", + "mime": "^4.1.0", + "postcss": "^8.5.18", + "postcss-import": "^14.0.2", + "postcss-url": "^10.1.4", + "prettier": "^2.5.1", + "react-test-renderer": "^19.2.5", + "recursive-copy": "^2.0.11", + "resolve": "^1.22.12", + "rollup": "^4.60.2", + "rollup-plugin-clear": "^2.0.7", + "rollup-plugin-command": "^1.1.3", + "rollup-plugin-license": "^3.7.1", + "rollup-plugin-livereload": "^2.0.5", + "rollup-plugin-postcss": "^4.0.2", + "rollup-plugin-re": "^1.0.7", + "sass": "^1.99.0", + "semver": "^7.3.2", + "shelljs": "^0.10.0", + "shx": "^0.4.0", + "ts-jest": "^29.4.9", + "ts-node": "^10.9.2", + "typescript": "^5.6.3", + "xml2js": "^0.6.2", + "zip-a-folder": "^6.1.1" + }, + "bin": { + "pluggable-widgets-tools": "bin/mx-scripts.js" + }, + "engines": { + "node": "^22.18.0" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.3.tgz", + "integrity": "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz", + "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.6.0", + "@parcel/watcher-darwin-arm64": "2.6.0", + "@parcel/watcher-darwin-x64": "2.6.0", + "@parcel/watcher-freebsd-x64": "2.6.0", + "@parcel/watcher-linux-arm-glibc": "2.6.0", + "@parcel/watcher-linux-arm-musl": "2.6.0", + "@parcel/watcher-linux-arm64-glibc": "2.6.0", + "@parcel/watcher-linux-arm64-musl": "2.6.0", + "@parcel/watcher-linux-x64-glibc": "2.6.0", + "@parcel/watcher-linux-x64-musl": "2.6.0", + "@parcel/watcher-win32-arm64": "2.6.0", + "@parcel/watcher-win32-x64": "2.6.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz", + "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz", + "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz", + "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz", + "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz", + "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz", + "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz", + "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz", + "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz", + "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz", + "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz", + "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz", + "integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.6.tgz", + "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@prettier/plugin-xml": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@prettier/plugin-xml/-/plugin-xml-1.2.0.tgz", + "integrity": "sha512-bFvVAZKs59XNmntYjyefn3K4TBykS6E+d6ZW8IcylAs88ZO+TzLhp0dPpi0VKfPzq1Nb+kpDnPRTiwb4zY6NgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xml-tools/parser": "^1.0.11", + "prettier": ">=2.3" + } + }, + "node_modules/@react-native/asset-utils": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/asset-utils/-/asset-utils-0.87.0.tgz", + "integrity": "sha512-XUSXMnBmQuBMuMWNWaXMGt4tLzb2yraCVO4b7TFCvEqiCg3ByhbQA0QNmPp+cdWgvmPmptCJwOrBWjPxppXgOA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/@react-native/babel-plugin-codegen": { + "version": "0.77.3", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.77.3.tgz", + "integrity": "sha512-UbjQY8vFCVD4Aw4uSRWslKa26l1uOZzYhhKzWWOrV36f2NnP9Siid2rPkLa+MIJk16G2UzDRtUrMhGuejxp9cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.3", + "@react-native/codegen": "0.77.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/babel-preset": { + "version": "0.77.3", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.77.3.tgz", + "integrity": "sha512-Cy1RoL5/nh2S/suWgfTuhUwkERoDN/Q2O6dZd3lcNcBrjd5Y++sBJGyBnHd9pqlSmOy8RLLBJZ9dOylycBOqzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/plugin-proposal-export-default-from": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-default-from": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.4", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.25.4", + "@babel/plugin-transform-classes": "^7.25.4", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-flow-strip-types": "^7.25.2", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.25.1", + "@babel/plugin-transform-literals": "^7.25.2", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-react-display-name": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.25.2", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/template": "^7.25.0", + "@react-native/babel-plugin-codegen": "0.77.3", + "babel-plugin-syntax-hermes-parser": "0.25.1", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/codegen": { + "version": "0.77.3", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.77.3.tgz", + "integrity": "sha512-Q6ZJCE7h6Z3v3DiEZUnqzHbgwF3ZILN+ACTx6qu/x2X1cL96AatKwdX92e0+7J9RFg6gdoFYJgRrW8Q6VnWZsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "glob": "^7.1.1", + "hermes-parser": "0.25.1", + "invariant": "^2.2.4", + "jscodeshift": "^17.0.0", + "nullthrows": "^1.1.1", + "yargs": "^17.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + } + }, + "node_modules/@react-native/community-cli-plugin": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.87.0.tgz", + "integrity": "sha512-Voaq6e6aSjLsFmOVofP1RHM0wD0Wo51AlIheIDAzXqgBKwEHEz2na4swNZ6sjWLZH/4Wm2ydy+8A/6RCyfht6w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@react-native/asset-utils": "0.87.0", + "@react-native/dev-middleware": "0.87.0", + "debug": "^4.4.0", + "invariant": "^2.2.4", + "metro": "^0.87.0", + "semver": "^7.1.3" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + }, + "peerDependencies": { + "@react-native-community/cli": "*", + "@react-native/metro-config": "0.87.0" + }, + "peerDependenciesMeta": { + "@react-native-community/cli": { + "optional": true + }, + "@react-native/metro-config": { + "optional": true + } + } + }, + "node_modules/@react-native/debugger-frontend": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.87.0.tgz", + "integrity": "sha512-TSUVjiX1cYganE6DcWVblDxs67x96u8/KqwhiHHViR6hGGjSIfIdNQcRLOEdXRpucuHB0awIFepBlw+34nTSUg==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/@react-native/debugger-shell": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.87.0.tgz", + "integrity": "sha512-8Om5Qm8Ln9zGZ93DJj7gZtlfT0y1wAFc/iWr3GFkIXSa6zwmC3DGzm4sm5eBTtdMl27EXmKACIOGsqztPH3Cvg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.6", + "debug": "^4.4.0", + "fb-dotslash": "0.5.8" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/@react-native/dev-middleware": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.87.0.tgz", + "integrity": "sha512-Fy4RMTCcAn402KGgxQZ4CwoURJdVkoPVxUbN7OTf9xy9xCOmBsOQQ+Xs1n6ULkNMUGcL3ptajpb39ual6r5ixg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.87.0", + "@react-native/debugger-shell": "0.87.0", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^0.3.0", + "connect": "^3.6.5", + "debug": "^4.4.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "open": "^7.0.3", + "serve-static": "^1.16.2", + "ws": "^7.5.10" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/ws": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@react-native/gradle-plugin": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.87.0.tgz", + "integrity": "sha512-CFOhPsxN4yS6vYjShSGKQiJHDPQFKmbiJG17zZfJuHTvJiaWz7JkpznE/P1RRKXdgvCmACrj31vxJiCHLlI7Gg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/@react-native/normalize-colors": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.87.0.tgz", + "integrity": "sha512-7aEGyrrquUqpbp+aEgOCp2xr0cTLYhJFCMfUqI4IoJddfwosra9Y5qtbrJU5Z/vBLndCmSIzABrWnHK0lxpwcA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@react-native/virtualized-lists": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.87.0.tgz", + "integrity": "sha512-rYQCtZupN7Iczm8fdGg3uKZUXhrxdZPRkg4BZK3RaQJ2HJ7n5tJNR2QPR3vSgpp5CATQggiGCuom1fNEC3T/8g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + }, + "peerDependencies": { + "@types/react": "^19.2.0", + "react": "*", + "react-native": "0.87.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-alias": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-5.1.1.tgz", + "integrity": "sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-babel": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.1.0.tgz", + "integrity": "sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@rollup/pluginutils": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + }, + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "29.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.3.tgz", + "integrity": "sha512-ZaOxZceP7SOUW7Lqw5IRVweSQYWaeIPnXIGLiB690EBA3FGJTO40EEr2L5yZplJWsgTCogILRSpcAe7+U0Otdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "fdir": "^6.2.0", + "is-reference": "1.2.1", + "magic-string": "^0.30.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0 || 14 >= 14.17" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-image": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-image/-/plugin-image-3.0.3.tgz", + "integrity": "sha512-qXWQwsXpvD4trSb8PeFPFajp8JLpRtqqOeNYRUKnEQNHm7e5UP7fuSRcbjQAJ7wDZBbnJvSdY5ujNBQd9B1iFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "mini-svg-data-uri": "^1.4.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", + "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-1.0.0.tgz", + "integrity": "sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "serialize-javascript": "^7.0.3", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-typescript": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.3.0.tgz", + "integrity": "sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.14.0||^3.0.0||^4.0.0", + "tslib": "*", + "typescript": ">=3.7.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + }, + "tslib": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-url": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-url/-/plugin-url-8.0.2.tgz", + "integrity": "sha512-5yW2LP5NBEgkvIRSSEdJkmxe5cUNZKG3eenKtfJvSkxVm/xTTu7w+ayBtNwhozl1ZnTUCU0xFaRQR+cBl2H7TQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "make-dir": "^3.1.0", + "mime": "^3.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-url/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@rollup/plugin-url/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@rollup/plugin-url/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", + "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", + "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", + "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", + "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", + "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", + "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", + "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", + "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", + "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", + "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", + "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", + "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", + "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", + "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", + "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", + "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", + "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", + "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", + "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", + "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", + "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", + "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", + "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", + "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", + "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", + "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz", + "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@swc/core": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.16.0.tgz", + "integrity": "sha512-zSdvEHxBg00WhUNtW/u58hhcdR33gjtMQvOBo8F7POWJDyjRCt/miKfhidT3hCc/118RUwNnlEAmxiihFMbK4Q==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.28" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.16.0", + "@swc/core-darwin-x64": "1.16.0", + "@swc/core-linux-arm-gnueabihf": "1.16.0", + "@swc/core-linux-arm64-gnu": "1.16.0", + "@swc/core-linux-arm64-musl": "1.16.0", + "@swc/core-linux-ppc64-gnu": "1.16.0", + "@swc/core-linux-s390x-gnu": "1.16.0", + "@swc/core-linux-x64-gnu": "1.16.0", + "@swc/core-linux-x64-musl": "1.16.0", + "@swc/core-win32-arm64-msvc": "1.16.0", + "@swc/core-win32-ia32-msvc": "1.16.0", + "@swc/core-win32-x64-msvc": "1.16.0" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.16.0.tgz", + "integrity": "sha512-SJQPl+xG/zB8bNjC/gTg3WOmOvz7EzlQD+VShfCKFYPNr2qvb+vATUY11vYEjnMWCn6wV8H8eAtjQrVflYyX5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.16.0.tgz", + "integrity": "sha512-ql2JVch8V5t1i+HxiiuD4oVDI1dOku4/e3QiCkplONrm3SLitqNAP+nztHN51fSG2IgGuOwpAi3hgA+ukT5yQg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.16.0.tgz", + "integrity": "sha512-PcdDBaRbe39y37h1rXVkhNy7mEU7f8b34KD761C68R23EsfMsj5oDPVddRzGdSRAvwwSfH0WSNEHgYmc/AJipg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.16.0.tgz", + "integrity": "sha512-t21IUztHQ/COucy7Kk9eIlehmq08H/hYq7aRA6fZox3S5ddi6TxWPK6e5S/+aTCf6+Od9qQ+LIpjHMiTy737vA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.16.0.tgz", + "integrity": "sha512-d9+iajbMB87b0umgbP+Gy3yBDSDgty4Q6H5pZ8fgTb/dOoKIwwynP4L4kvWCOFg2i49kxmAAUs1uJZh9s0E+RQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.16.0.tgz", + "integrity": "sha512-QRpeKGOg+B0qmo3BFU+6rL/gpoKYYJ7OFSMf5DNMafohYZ/iq2qvAH9Gcrf8NxROj3iooKOVewJ+YgahH1nSLw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.16.0.tgz", + "integrity": "sha512-q+Vr/hmHCcRXT/WFzOJC+T6GGEEtq2iaTtmyLfxO7yzu4ckgcqSNkg9m181wfNhuMwfNBoBhOfwQCnLsGZ5F4g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.16.0.tgz", + "integrity": "sha512-DWVBc3QnpsSgKoq8N4rmZeZa5r/XrHdLkITsExN/tvTdqPtAPDPt+Ysy33OfgBlyN8lNe4xwsXWe6DXlRkJeRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.16.0.tgz", + "integrity": "sha512-6XCgDSc1HPf/5dpjvABhKHICiBcsuZyW3hQMkn8sxel0TqprkJGp+H4iaBYIUTPixhrBub2hBPtfjcZLE6yL3w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.16.0.tgz", + "integrity": "sha512-T/+9VVCZJ3AKEth9IP3U9AJ2YscQq+7LUqRTvfR4a2q36+Ri22oOwUizpAKOqQ42vb2Y/kOa4TOcJOfHoDIT/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.16.0.tgz", + "integrity": "sha512-Pr1lsR/PMs8ndL0UWMrW8nLZ7H7sspIxBRDdjL8f+YJ/FJNASgzfunbVVXAqj0csgIJYHPZy+OW9smjFmk1Rcg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.16.0.tgz", + "integrity": "sha512-ktdeYLgOQdaonvsj5tJijqgpb0wk7gfF80wCFVA0kucI1hhSUIyfcGbjo5+9sdqv38OhMnTdLoA6xbqgOgPQjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/jest": { + "version": "0.2.39", + "resolved": "https://registry.npmjs.org/@swc/jest/-/jest-0.2.39.tgz", + "integrity": "sha512-eyokjOwYd0Q8RnMHri+8/FS1HIrIUKK/sRrFp8c1dThUOfNeCWbLmBP1P5VsKdvmkd25JaH+OKYwEYiAYg9YAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/create-cache-key-function": "^30.0.0", + "@swc/counter": "^0.1.3", + "jsonc-parser": "^3.2.0" + }, + "engines": { + "npm": ">= 7.0.0" + }, + "peerDependencies": { + "@swc/core": "*" + } + }, + "node_modules/@swc/types": { + "version": "0.1.28", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.28.tgz", + "integrity": "sha512-V6Mnml8v09QALx6K0elJ7o9K/MkVDtW3t6L+7Ou/JcWtb3xwId2AH4FeOceySd2JaO87IMw4+6vSZxLm34LPbw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/react-native": { + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/@testing-library/react-native/-/react-native-13.3.3.tgz", + "integrity": "sha512-k6Mjsd9dbZgvY4Bl7P1NIpePQNi+dfYtlJ5voi9KQlynxSyQkfOgJmYGCYmw/aSgH/rUcFvG8u5gd4npzgRDyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-matcher-utils": "^30.0.5", + "picocolors": "^1.1.1", + "pretty-format": "^30.0.5", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "jest": ">=29.0.0", + "react": ">=18.2.0", + "react-native": ">=0.71", + "react-test-renderer": ">=18.2.0" + }, + "peerDependenciesMeta": { + "jest": { + "optional": true + } + } + }, + "node_modules/@testing-library/react-native/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/react-native/node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.4", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.4.tgz", + "integrity": "sha512-QCGwP6QrjypBLwyj5cuyfVamkaIEy/XGY+1VDehbtbQqOggYmTFpFOdWR5mPz14vX8vXLMVjDHlRNBcClyO9ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.13.tgz", + "integrity": "sha512-gcLdvR9HO1ZJBypsOGqaP6TFEzb6vIta0KSTLt9NAQ6pXQO3cRgSVyCN6pzYqI9DlJgY71XKO0dpDhCf08b3pg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/big.js": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@types/big.js/-/big.js-6.2.2.tgz", + "integrity": "sha512-e2cOW9YlVzFY2iScnGBBkplKsrn2CsObHQ2Hiw4V1sSyiGbgWL8IyqE3zFi1Pt5o1pdAtYkDAIsF3KKUPjdzaA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, + "node_modules/@types/jest/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@types/jest/node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jsdom": { + "version": "21.1.7", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", + "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", + "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-1mAINjtQCXXeLkJ9ehXkwOcBpqtLxiVtKhpUf83DdRNdQKV0iXZpaHYqRr7nj+wvxuJzoAmAwXI+sCNMv1CzLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/testing-library__jest-dom": { + "version": "5.14.9", + "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", + "integrity": "sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/jest": "*" + } + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.67.0.tgz", + "integrity": "sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/type-utils": "8.67.0", + "@typescript-eslint/utils": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.67.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.67.0.tgz", + "integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", + "integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.67.0", + "@typescript-eslint/types": "^8.67.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.67.0.tgz", + "integrity": "sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", + "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.67.0.tgz", + "integrity": "sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/utils": "8.67.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.67.0.tgz", + "integrity": "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", + "integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.67.0", + "@typescript-eslint/tsconfig-utils": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.67.0.tgz", + "integrity": "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz", + "integrity": "sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.67.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@xml-tools/parser": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@xml-tools/parser/-/parser-1.0.11.tgz", + "integrity": "sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "chevrotain": "7.1.1" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/anser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", + "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", + "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8", + "core-js-compat": "^3.48.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-syntax-hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.25.1.tgz", + "integrity": "sha512-IVNpGzboFLfXZUAwkLFcI/bnqVbwky0jP3eBno4HKtqvQJAHBLdgxiG6lQ4to0+Q/YCN3PO0od5NZwIKyY4REQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-parser": "0.25.1" + } + }, + "node_modules/babel-plugin-transform-flow-enums": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", + "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-flow": "^7.12.1" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peer": true + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.14.tgz", + "integrity": "sha512-JyJ954WzuIR8/FFzX0o5krdSTrBAkcCSRfWSleRsIHSWV+cZe2FI1PKggVkFke1hBldRs+LRxUczzE9iPmgZww==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/big.js": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.2.tgz", + "integrity": "sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/bigjs" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chevrotain": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-7.1.1.tgz", + "integrity": "sha512-wy3mC1x4ye+O+QkEinVJkPf5u2vsrDIYW9G7ZuwFl6v/Yu0LwUuT2POsb+NUWApebyxfkQq6+yDfRExbnI5rcw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "regexp-to-ast": "0.5.0" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chrome-launcher": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/chromium-edge-launcher": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.3.0.tgz", + "integrity": "sha512-p03azHlGjtyRvFEee3cyvtsRYdniSkwjkzmM/KmVnqT5d7QkkwpJBhis/zCLMYdQMVJ5tt140TBNqqrZPaWeFA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.1.tgz", + "integrity": "sha512-Ca8swihM+/4yKecYHY52kgJd300hi2lADU/a1RxNTRe+RJ9jvqQlESpbz9DnG9mowez8qwXHB8qYdIUw9e+F5Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/commenting": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/commenting/-/commenting-1.1.0.tgz", + "integrity": "sha512-YeNK4tavZwtH7jEgK1ZINXzLKm6DZdEMfsaaieOsCAN0S8vsY7UeuO3Q7d/M018EFgE+IeUAuBOKkFccBZsUZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-with-sourcemaps": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", + "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "dev": true, + "license": "ISC", + "dependencies": { + "source-map": "^0.6.1" + } + }, + "node_modules/concat-with-sourcemaps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-js": { + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.50.0.tgz", + "integrity": "sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.50.0.tgz", + "integrity": "sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.7" + }, + "engines": { + "node": ">=6.4.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-declaration-sorter": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", + "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-tree/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "5.1.15", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.15.tgz", + "integrity": "sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-preset-default": "^5.2.14", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-preset-default": { + "version": "5.2.14", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz", + "integrity": "sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-declaration-sorter": "^6.3.1", + "cssnano-utils": "^3.1.0", + "postcss-calc": "^8.2.3", + "postcss-colormin": "^5.3.1", + "postcss-convert-values": "^5.1.3", + "postcss-discard-comments": "^5.1.2", + "postcss-discard-duplicates": "^5.1.0", + "postcss-discard-empty": "^5.1.1", + "postcss-discard-overridden": "^5.1.0", + "postcss-merge-longhand": "^5.1.7", + "postcss-merge-rules": "^5.1.4", + "postcss-minify-font-values": "^5.1.0", + "postcss-minify-gradients": "^5.1.1", + "postcss-minify-params": "^5.1.4", + "postcss-minify-selectors": "^5.2.1", + "postcss-normalize-charset": "^5.1.0", + "postcss-normalize-display-values": "^5.1.0", + "postcss-normalize-positions": "^5.1.1", + "postcss-normalize-repeat-style": "^5.1.1", + "postcss-normalize-string": "^5.1.0", + "postcss-normalize-timing-functions": "^5.1.0", + "postcss-normalize-unicode": "^5.1.1", + "postcss-normalize-url": "^5.1.0", + "postcss-normalize-whitespace": "^5.1.1", + "postcss-ordered-values": "^5.1.3", + "postcss-reduce-initial": "^5.1.2", + "postcss-reduce-transforms": "^5.1.0", + "postcss-svgo": "^5.1.0", + "postcss-unique-selectors": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cuint": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", + "integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo-projection": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-4.0.0.tgz", + "integrity": "sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==", + "license": "ISC", + "dependencies": { + "commander": "7", + "d3-array": "1 - 3", + "d3-geo": "1.12.0 - 3" + }, + "bin": { + "geo2svg": "bin/geo2svg.js", + "geograticule": "bin/geograticule.js", + "geoproject": "bin/geoproject.js", + "geoquantize": "bin/geoquantize.js", + "geostitch": "bin/geostitch.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo-projection/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/electron-to-chromium": { + "version": "1.5.407", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.407.tgz", + "integrity": "sha512-4R8XgQOdfxexCd/u63lRm6wCHjECwI45MV9wxAs2ggtfWe2hwlo1ql97jKsju2IcJ+jFSTwBssyYoiWhh7mauQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "is-callable": "^1.2.7", + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.2.tgz", + "integrity": "sha512-/IGJ6+Dka158JnP5n5YFMOszjDWrXggGz1LaK/guZq9vZTmniaKlHcsscvkAhn9y4U+BU3JuUdYvtAMcv30y4A==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-jest": { + "version": "29.16.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.16.1.tgz", + "integrity": "sha512-tfxOIsjzaBud+f74aLbBMRcnrztt5eCIgnAdeoGdnzMAQ4IdAa/s/p8Ls55mk9MC79N3j2jbv4Qetz6Hclbcfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.0.0" + }, + "engines": { + "node": "^20.12.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^8.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "jest": "*", + "typescript": ">=4.8.4 <8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", + "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "eslint": ">=5.0.0", + "prettier": ">=1.13.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-promise": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.3.1.tgz", + "integrity": "sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz", + "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.4.1", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/expect/node_modules/jest-util": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", + "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0", + "peer": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-patch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", + "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-dotslash": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz", + "integrity": "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "peer": true, + "bin": { + "dotslash": "bin/dotslash" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-cache-dir/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-cache-dir/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-cache-dir/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-cache-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-cache-dir/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-cache-dir/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/find-cache-dir/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/find-cache-dir/node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-cache-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/flow-enums-runtime": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", + "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/flow-estree": { + "version": "0.328.0", + "resolved": "https://registry.npmjs.org/flow-estree/-/flow-estree-0.328.0.tgz", + "integrity": "sha512-uMB3dC4nfZYn+dd7/PYkyAK1mqBR4TJ4TWRdOjubpb/ObrLvPFdFSmMTq55fqttCCTygQ4NtD1Zdd+wcjC5t0g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/flow-parser": { + "version": "0.328.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.328.0.tgz", + "integrity": "sha512-F+Ik2Of7ndl2FyPmZ77NrhbZp9lYjIsreDXeq4fo0sjxa3SIFv48QAszBqvPeMMzmw7JzqySJUxGR8CSD7EdXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flow-estree": "0.328.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", + "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/generic-names": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/generic-names/-/generic-names-4.0.0.tgz", + "integrity": "sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils": "^3.2.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/harmony-reflect": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", + "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==", + "dev": true, + "license": "(Apache-2.0 OR MPL-1.1)" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-compiler": { + "version": "250829098.0.16", + "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.16.tgz", + "integrity": "sha512-xsgzk+mUyvt9t1nUbF8USBlYxajTUtPJhVZ86q85s/SEoMKCF+52YZcudb0ENSnV3T3lV9mgB3s6R7+pH90zgw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg==", + "dev": true, + "license": "ISC" + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/identity-obj-proxy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", + "integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==", + "dev": true, + "license": "MIT", + "dependencies": { + "harmony-reflect": "^1.4.6" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-size": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", + "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, + "node_modules/immutable": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", + "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz", + "integrity": "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "import-from": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/import-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", + "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jasmine": { + "version": "3.99.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.99.0.tgz", + "integrity": "sha512-YIThBuHzaIIcjxeuLmPD40SjxkEcc8i//sGMDKCgkRMVgIwRJf5qyExtlJpQeh7pkeoBSOe6lQEdg+/9uKg9mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^7.1.6", + "jasmine-core": "~3.99.0" + }, + "bin": { + "jasmine": "bin/jasmine.js" + } + }, + "node_modules/jasmine-core": { + "version": "3.99.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.99.1.tgz", + "integrity": "sha512-Hu1dmuoGcZ7AfyynN3LsfruwMbxMALMka+YtZeGoLuDEySVmVAPaonkNoBRIw/ectu8b9tVQCJNgp4a4knp+tg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz", + "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.3.0", + "@jest/types": "30.3.0", + "import-local": "^3.2.0", + "jest-cli": "30.3.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz", + "integrity": "sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.1.1", + "jest-util": "30.3.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-changed-files/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-changed-files/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-changed-files/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-changed-files/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-changed-files/node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz", + "integrity": "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "co": "^4.6.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "p-limit": "^3.1.0", + "pretty-format": "30.3.0", + "pure-rand": "^7.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus/node_modules/@jest/diff-sequences": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", + "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/jest-diff": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", + "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.3.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus/node_modules/jest-matcher-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", + "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.3.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus/node_modules/jest-message-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.3.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus/node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus/node_modules/pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-cli": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.3.0.tgz", + "integrity": "sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "yargs": "^17.7.2" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-cli/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-cli/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-cli/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-cli/node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-config": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.3.0.tgz", + "integrity": "sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.3.0", + "@jest/types": "30.3.0", + "babel-jest": "30.3.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-circus": "30.3.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-runner": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "parse-json": "^5.2.0", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "esbuild-register": ">=3.4.0", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild-register": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-config/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-config/node_modules/@jest/transform": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", + "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.3.0", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", + "chalk": "^4.1.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-config/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/babel-jest": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", + "integrity": "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "30.3.0", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.3.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-0" + } + }, + "node_modules/jest-config/node_modules/babel-jest/node_modules/babel-preset-jest": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz", + "integrity": "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "30.3.0", + "babel-preset-current-node-syntax": "^1.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" + } + }, + "node_modules/jest-config/node_modules/babel-plugin-istanbul": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-config/node_modules/babel-plugin-jest-hoist": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz", + "integrity": "sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/babel__core": "^7.20.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-config/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-config/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/jest-config/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "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", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-config/node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-config/node_modules/jest-haste-map": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", + "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "picomatch": "^4.0.3", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/jest-config/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-config/node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-config/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-config/node_modules/pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-config/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-config/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-config/node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/jest-diff": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", + "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.4.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz", + "integrity": "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "jest-util": "30.3.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each/node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each/node_modules/pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-environment-jsdom": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.3.0.tgz", + "integrity": "sha512-RLEOJy6ip1lpw0yqJ8tB3i88FC7VBz7i00Zvl2qF71IdxjS98gC9/0SPWYIBVXHm5hgCYK0PAlSlnHGGy9RoMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/environment-jsdom-abstract": "30.3.0", + "jsdom": "^26.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-environment-node": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz", + "integrity": "sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-mock": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/jest-mock": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", + "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-haste-map/node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map/node_modules/@sinclair/typebox": { + "version": "0.27.12", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.12.tgz", + "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-haste-map/node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map/node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jest-jasmine2": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-30.3.0.tgz", + "integrity": "sha512-oNNTvA5UBmxQuimsK7G3l1wLIXaUlQ81/v8zaTC1Y9thJkjbOV6v7mBPFiruk98QpplYkS65S2u1ihvW66kxLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "co": "^4.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "p-limit": "^3.1.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/@jest/diff-sequences": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", + "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-jasmine2/node_modules/jest-diff": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", + "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.3.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/jest-matcher-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", + "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.3.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/jest-message-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.3.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-junit": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-17.0.0.tgz", + "integrity": "sha512-RYWCkq4j59gUXj5DsgbIE7xFBZzu1gtibPhyjSjMmGaOTLnqlXhg7x9zuGCwgbCuMAyoyvk0Mi8wSrRR5uOeLA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mkdirp": "^1.0.4", + "strip-ansi": "^6.0.1", + "uuid": "^14.0.0", + "xml": "^1.0.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/jest-leak-detector": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz", + "integrity": "sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-leak-detector/node_modules/pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-matcher-utils": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz", + "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.4.1", + "pretty-format": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", + "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.4.1", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-util": "30.4.1", + "picomatch": "^4.0.3", + "pretty-format": "30.4.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/jest-util": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", + "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util/node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", + "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@types/node": "*", + "jest-util": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock/node_modules/jest-util": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", + "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz", + "integrity": "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz", + "integrity": "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve-dependencies/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve/node_modules/jest-haste-map": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", + "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "picomatch": "^4.0.3", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/jest-resolve/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve/node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz", + "integrity": "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.3.0", + "@jest/environment": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-leak-detector": "30.3.0", + "jest-message-util": "30.3.0", + "jest-resolve": "30.3.0", + "jest-runtime": "30.3.0", + "jest-util": "30.3.0", + "jest-watcher": "30.3.0", + "jest-worker": "30.3.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner/node_modules/@jest/transform": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", + "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.3.0", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", + "chalk": "^4.1.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/babel-plugin-istanbul": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-runner/node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-runner/node_modules/jest-haste-map": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", + "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "picomatch": "^4.0.3", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/jest-runner/node_modules/jest-message-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.3.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner/node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner/node_modules/pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-runner/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-runner/node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz", + "integrity": "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/globals": "30.3.0", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime/node_modules/@jest/transform": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", + "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.3.0", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", + "chalk": "^4.1.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/babel-plugin-istanbul": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-runtime/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-runtime/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/jest-runtime/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "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", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-runtime/node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-runtime/node_modules/jest-haste-map": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", + "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "picomatch": "^4.0.3", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/jest-runtime/node_modules/jest-message-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.3.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime/node_modules/jest-mock": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", + "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime/node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-runtime/node_modules/pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-runtime/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-runtime/node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz", + "integrity": "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.3.0", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.3.0", + "graceful-fs": "^4.2.11", + "jest-diff": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "pretty-format": "30.3.0", + "semver": "^7.7.2", + "synckit": "^0.11.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/@jest/diff-sequences": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", + "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/@jest/expect-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz", + "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/@jest/transform": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", + "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.3.0", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", + "chalk": "^4.1.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/babel-plugin-istanbul": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-snapshot/node_modules/expect": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.3.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/jest-diff": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", + "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.3.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/jest-haste-map": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", + "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "picomatch": "^4.0.3", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/jest-snapshot/node_modules/jest-matcher-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", + "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.3.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/jest-message-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.3.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/jest-mock": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", + "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-snapshot/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-snapshot/node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/@sinclair/typebox": { + "version": "0.27.12", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.12.tgz", + "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-util/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-validate": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", + "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-validate/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-watcher": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz", + "integrity": "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "jest-util": "30.3.0", + "string-length": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-watcher/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-watcher/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-watcher/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-watcher/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-watcher/node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", + "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.3.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker/node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jest/node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest/node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz", + "integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsc-safe-url": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", + "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", + "dev": true, + "license": "0BSD", + "peer": true + }, + "node_modules/jscodeshift": { + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-17.4.0.tgz", + "integrity": "sha512-i3ESKiiTsGynxzTg5BhsZViD0ai72/6SsI1efDZxG6/5KCoElsmquxtyhXK5lpEgoO7MTNpYkjFEdEL97SkBNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/preset-flow": "^7.24.7", + "@babel/preset-typescript": "^7.24.7", + "@babel/register": "^7.24.6", + "flow-parser": "0.*", + "graceful-fs": "^4.2.4", + "neo-async": "^2.5.0", + "picocolors": "^1.0.1", + "picomatch": "^4.0.2", + "recast": "^0.23.11", + "tmp": "^0.2.3", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "jscodeshift": "bin/jscodeshift.js" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + }, + "peerDependenciesMeta": { + "@babel/preset-env": { + "optional": true + } + } + }, + "node_modules/jscodeshift/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jscodeshift/node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-pretty-compact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz", + "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/junk": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/junk/-/junk-1.0.3.tgz", + "integrity": "sha512-3KF80UaaSSxo8jVnRYtMKNGFOoVPBdkkVPsw+Ad0y4oxKXPduS6G6iHkrf69yJVff/VAaYXkV42rtZ7daJxU3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lighthouse-logger": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "debug": "^2.6.9", + "marky": "^1.2.2" + } + }, + "node_modules/lighthouse-logger/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/lighthouse-logger/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/livereload": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/livereload/-/livereload-0.9.3.tgz", + "integrity": "sha512-q7Z71n3i4X0R9xthAryBdNGVGAO2R5X+/xXpmKeuPMrteg+W2U8VusTKV3YiJbXZwKsOlFlHe+go6uSNjfxrZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.0", + "livereload-js": "^3.3.1", + "opts": ">= 1.2.0", + "ws": "^7.4.3" + }, + "bin": { + "livereload": "bin/livereload.js" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/livereload-js": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-3.4.1.tgz", + "integrity": "sha512-5MP0uUeVCec89ZbNOT/i97Mc+q3SxXmiUGhRFOTmhrGPn//uWVQdCvcLJDy64MSBR5MidFdOR7B9viumoavy6g==", + "dev": true, + "license": "MIT" + }, + "node_modules/livereload/node_modules/ws": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/loader-utils": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", + "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/lzma": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/lzma/-/lzma-2.3.2.tgz", + "integrity": "sha512-DcfiawQ1avYbW+hsILhF38IKAlnguc/fjHrychs9hdxe4qLykvhT5VTGNs5YRWgaNePh7NTxGD4uv4gKsRomCQ==", + "dev": true, + "license": "MIT", + "bin": { + "lzma.js": "bin/lzma.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-dir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-5.1.0.tgz", + "integrity": "sha512-IfpFq6UM39dUNiphpA6uDezNx/AvWyhwfICWPR3t1VspkgkMZrL+Rk1RbN1bx+aeNYwOrqGJgEgV3yotk+ZUVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/marky": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", + "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/maximatch": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/maximatch/-/maximatch-0.1.0.tgz", + "integrity": "sha512-9ORVtDUFk4u/NFfo0vG/ND/z7UQCVZBL539YW0+U1I7H1BkZwizcPx5foFv7LCPcBnm2U6RjFnQOsIvN4/Vm2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-differ": "^1.0.0", + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "minimatch": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/maximatch/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/maximatch/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/maximatch/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/mendix": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/mendix/-/mendix-11.13.0.tgz", + "integrity": "sha512-E9nacFc2Pd61EBCZD8OcEGmc2q+t+c7rktJ0iDSbJaSnsq4j4EqW7YrJluvn2B7BKFBiYFVmWvZunuXFfF6fhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/big.js": "^6.0.0", + "@types/react": "~19.2.14" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/metro": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.87.0.tgz", + "integrity": "sha512-fRqFhSzQhLNQSCvJFeuRzBRXAOOKXf1O8d2cvmMtG6yFR0jCllQ7vBsXoLP18yuqtf+N1XwWXTPF11eWy9q6dQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "accepts": "^2.0.0", + "ci-info": "^2.0.0", + "connect": "^3.6.5", + "debug": "^4.4.0", + "error-stack-parser": "^2.0.6", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "hermes-parser": "0.36.1", + "image-size": "^1.0.2", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "jsc-safe-url": "^0.2.2", + "lodash.throttle": "^4.1.1", + "metro-babel-transformer": "0.87.0", + "metro-cache": "0.87.0", + "metro-cache-key": "0.87.0", + "metro-config": "0.87.0", + "metro-core": "0.87.0", + "metro-file-map": "0.87.0", + "metro-resolver": "0.87.0", + "metro-runtime": "0.87.0", + "metro-source-map": "0.87.0", + "metro-symbolicate": "0.87.0", + "metro-transform-plugins": "0.87.0", + "metro-transform-worker": "0.87.0", + "mime-types": "^3.0.1", + "nullthrows": "^1.1.1", + "serialize-error": "^2.1.0", + "source-map": "^0.5.6", + "throat": "^5.0.0", + "ws": "^7.5.10", + "yargs": "^17.6.2" + }, + "bin": { + "metro": "src/cli.js" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/metro-babel-transformer": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.87.0.tgz", + "integrity": "sha512-IEn1K1FyY4J1sA5y6zqDjf2OkfmpTEqhZOeP6MJX8HepSW0cuHGw1m8bYOdv2adkG3XUE9dtM0csUs0gP/Xa5w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "hermes-parser": "0.36.1", + "metro-cache-key": "0.87.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/metro-babel-transformer/node_modules/hermes-estree": { + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.36.1.tgz", + "integrity": "sha512-guv1nQ6IJ7S83NRFPWc3SA7IBZrdNC9kapwOq6uXvF4wP+sDCgjzQbKPCoyYmoyZRzztF/n/c36l/rccCZSiCw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/metro-babel-transformer/node_modules/hermes-parser": { + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.36.1.tgz", + "integrity": "sha512-GApNk4zLHi2UWoWZZkx7LNCOSzLSc5lB55pZ/PhK7ycFeg7u5LcF88p/WbpIi1XUDtE0MpHE3uRR3u3KB7TjSQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "hermes-estree": "0.36.1" + } + }, + "node_modules/metro-cache": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.87.0.tgz", + "integrity": "sha512-146vS1BMSKcp99jddOhFBfHwzUEWN35NrsnSJDF2sQQ0ZT5OsBcOjd574PM233TWEZISRJ5DOK+vokD+1ubx+w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "exponential-backoff": "^3.1.1", + "flow-enums-runtime": "^0.0.6", + "https-proxy-agent": "^7.0.5", + "metro-core": "0.87.0" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/metro-cache-key": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.87.0.tgz", + "integrity": "sha512-Q+MPt6jl0zQogr4Q02WaJK6HY+GtE5A0nzj8kIV1Owgrx6OMNvm6scPTr1SM/R4LpCE8EH/Y5qfbXQ84GHTr0Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/metro-config": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.87.0.tgz", + "integrity": "sha512-yZ9QAIzWH9MxwrzwRlX/CBGRWOT14l7klSDYg8hdtSdnoUs5A7MQRdHE2KB9iHVzGQW5wgWM5aXJswNeWbSQPA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "connect": "^3.6.5", + "flow-enums-runtime": "^0.0.6", + "jest-validate": "^29.7.0", + "metro": "0.87.0", + "metro-cache": "0.87.0", + "metro-core": "0.87.0", + "metro-runtime": "0.87.0" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/metro-config/node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/metro-config/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/metro-config/node_modules/@sinclair/typebox": { + "version": "0.27.12", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.12.tgz", + "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/metro-config/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/metro-config/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/metro-config/node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/metro-config/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/metro-config/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/metro-core": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.87.0.tgz", + "integrity": "sha512-yW57+pCOHRC/CJZ99GA2PTd+30dORwDAjUPRCokj91IWW5In9Jwtt2FB5wACrGO8P0GHTyVHdTwDNyZsNndUbA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "lodash.throttle": "^4.1.1", + "metro-resolver": "0.87.0" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/metro-file-map": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.87.0.tgz", + "integrity": "sha512-Dc57t8jsINwA90bbVlqaeDlxf1rVGgj5SmOEnOMbaHNUM/HCYYTJxPV8SRdOBwh7qTz/biO9vaQvBTjRBgbbsg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.0", + "fb-watchman": "^2.0.0", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "nullthrows": "^1.1.1", + "walker": "^1.0.7" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/metro-file-map/node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/metro-file-map/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/metro-minify-terser": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.87.0.tgz", + "integrity": "sha512-tPa0O983PDutFu3LXbArRH5NduogcKrvW6fs9VHhksTKUA1iqDyoD1ZSj/Me52xJ6T9/9pOwIVyj9ulKc/zMkg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "terser": "^5.15.0" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/metro-resolver": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.87.0.tgz", + "integrity": "sha512-Xl3M9R3KToaHJvXlI2lSOxtYHitzxite+195DSi00HL9PcS7Xik5+3xlRjfkKb3FA86SZxYPj+WJwlcfgaZoxg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/metro-runtime": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.87.0.tgz", + "integrity": "sha512-XsXZkgEwI0ZMYSBfvOMAbenzwa60XlObXJ27g6/Khgrz9ESbiBbAsd7hR62G2jRBYOhGAzG61Gk22dpRJj/mdw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.25.0", + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/metro-source-map": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.87.0.tgz", + "integrity": "sha512-31BrYqu1c2co93rF1LN9Pw+7g+BrfDyxJkNQWrYm+pfA/+eVYVumF7tFMHbXLePLmHfhvSgVvbK6su1Oyiw1ng==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-symbolicate": "0.87.0", + "nullthrows": "^1.1.1", + "ob1": "0.87.0", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/metro-symbolicate": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.87.0.tgz", + "integrity": "sha512-uOpTxAXu74N+RSujUZ78L6gjI6bDdnz6XuW+AIUNuubZDEQPIpaX0StzIb0GMeQWP6zfHOHwFrWPP5Iquu1GXw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-source-map": "0.87.0", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "bin": { + "metro-symbolicate": "src/index.js" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/metro-transform-plugins": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.87.0.tgz", + "integrity": "sha512-i8keUe9+BaSwMuQM26DGheElCpTtflAKIrSwJAm8ZsgDb50RAUQus+e6zt2suaJXJ1OxZa7vqgtqoZxdniM6Fw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/metro-transform-worker": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.87.0.tgz", + "integrity": "sha512-YftLzNJxCTYxEN5k4AzR8KYwiENTEuz30L+4QeoMrtDd+U8mDThZg/ArR3JVRd8LaikwPOjVAS5SP3xPJN0AaA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "metro": "0.87.0", + "metro-babel-transformer": "0.87.0", + "metro-cache": "0.87.0", + "metro-cache-key": "0.87.0", + "metro-minify-terser": "0.87.0", + "metro-source-map": "0.87.0", + "metro-transform-plugins": "0.87.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/metro/node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/metro/node_modules/hermes-estree": { + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.36.1.tgz", + "integrity": "sha512-guv1nQ6IJ7S83NRFPWc3SA7IBZrdNC9kapwOq6uXvF4wP+sDCgjzQbKPCoyYmoyZRzztF/n/c36l/rccCZSiCw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/metro/node_modules/hermes-parser": { + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.36.1.tgz", + "integrity": "sha512-GApNk4zLHi2UWoWZZkx7LNCOSzLSc5lB55pZ/PhK7ycFeg7u5LcF88p/WbpIi1XUDtE0MpHE3uRR3u3KB7TjSQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "hermes-estree": "0.36.1" + } + }, + "node_modules/metro/node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/metro/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/metro/node_modules/ws": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", + "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa" + ], + "license": "MIT", + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "dev": true, + "license": "MIT", + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-exports-info": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz", + "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", + "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nwsapi": { + "version": "2.2.24", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.24.tgz", + "integrity": "sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ob1": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.87.0.tgz", + "integrity": "sha512-8Q8sKCiUwsxgSmjDtVWyRgmxsgeJXXam3oQH6Id8ADfNaJMV6GZKyeAl8+pGVVdgwAZabfk5+aExle7AP/nZiA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/opts": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/opts/-/opts-2.0.2.tgz", + "integrity": "sha512-k41FwbcLnlgnFh69f4qdUfvDQ+5vaSDnVPFI/y5XuhKRq97EnVVneO9F1ESVCdiVu4fCS2L8usX3mU331hB7pg==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/own-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.2.tgz", + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/package-name-regex": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/package-name-regex/-/package-name-regex-2.0.6.tgz", + "integrity": "sha512-gFL35q7kbE/zBaPA3UKhp2vSzcPYx2ecbYuwv1ucE9Il6IIgBDweBlH8D68UFGZic2MkllKa2KHCfC1IQBQUYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/dword-design" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-calc": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", + "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.9", + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-colormin": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.1.tgz", + "integrity": "sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "colord": "^2.9.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-convert-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", + "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-comments": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", + "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-empty": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-import": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", + "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-merge-longhand": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", + "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-merge-rules": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz", + "integrity": "sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^3.1.0", + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", + "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", + "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "colord": "^2.9.1", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-params": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", + "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", + "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-modules": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-4.3.1.tgz", + "integrity": "sha512-ItUhSUxBBdNamkT3KzIZwYNNRFKmkJrofvC2nWab3CPKhYBQ1f27XXh1PAPE27Psx58jeelPsxWB/+og+KEH0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "generic-names": "^4.0.0", + "icss-replace-symbols": "^1.1.0", + "lodash.camelcase": "^4.3.0", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "string-hash": "^1.1.1" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", + "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", + "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", + "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-string": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", + "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", + "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", + "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", + "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", + "dev": true, + "license": "MIT", + "dependencies": { + "normalize-url": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", + "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-ordered-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", + "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz", + "integrity": "sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", + "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", + "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^2.7.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", + "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-url": { + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/postcss-url/-/postcss-url-10.1.4.tgz", + "integrity": "sha512-/oBzyLOHQvXvVr/7bzZOFD5lYTy1nomVE4aMA9eY5KQsHfWLDIzb86q8XoUsmrj2xKoGYMAvd884EzJEQzuXIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "make-dir": "~3.1.0", + "mime": "~2.5.2", + "minimatch": "^3.1.5", + "xxhashjs": "~0.2.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-url/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss-url/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/postcss-url/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/postcss-url/node_modules/mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/postcss-url/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/postcss-url/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "asap": "~2.0.6" + } + }, + "node_modules/promise.series": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz", + "integrity": "sha512-VWQJyU2bcDTgZw8kpfBpB/ejZASlCrzwz5f2hjb/zlujOEB4oeiAhHygAWq8ubsX2GVkD4kCU5V2dwOTaCY5EQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "inherits": "~2.0.3" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-devtools-core": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz", + "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "shell-quote": "^1.6.1", + "ws": "^7" + } + }, + "node_modules/react-devtools-core/node_modules/ws": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-is-18": { + "name": "react-is", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-is-19": { + "name": "react-is", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-native": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.87.0.tgz", + "integrity": "sha512-e7CeZOm1wBksIISI5oLNmH4/GJCqWrfNphF7PuBsLY2qUM6mtzqL23N7iLTW6U7LF45qYi+a4iAp5Y1kSk1vVw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@react-native/asset-utils": "0.87.0", + "@react-native/codegen": "0.87.0", + "@react-native/community-cli-plugin": "0.87.0", + "@react-native/gradle-plugin": "0.87.0", + "@react-native/normalize-colors": "0.87.0", + "@react-native/virtualized-lists": "0.87.0", + "anser": "^1.4.9", + "ansi-regex": "^5.0.0", + "babel-plugin-syntax-hermes-parser": "0.36.1", + "base64-js": "^1.5.1", + "commander": "^12.0.0", + "flow-enums-runtime": "^0.0.6", + "hermes-compiler": "250829098.0.16", + "invariant": "^2.2.4", + "memoize-one": "^5.0.0", + "metro-runtime": "^0.87.0", + "metro-source-map": "^0.87.0", + "nullthrows": "^1.1.1", + "pretty-format": "^29.7.0", + "promise": "^8.3.0", + "react-devtools-core": "^6.1.5", + "react-refresh": "^0.14.0", + "regenerator-runtime": "^0.13.2", + "scheduler": "0.27.0", + "semver": "^7.1.3", + "stacktrace-parser": "^0.1.10", + "tinyglobby": "^0.2.15", + "whatwg-fetch": "^3.0.0", + "ws": "^7.5.10", + "yargs": "^17.6.2" + }, + "bin": { + "react-native": "cli.js" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + }, + "peerDependencies": { + "@types/react": "^19.1.1", + "react": "^19.2.3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-native/node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/react-native/node_modules/@react-native/codegen": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.87.0.tgz", + "integrity": "sha512-odcBGR0A9Ee83A/XvzOp9juGRkSQOQcI4ZYs+3H20MySMxvZJ34/2nVHoxdmj0YY0Mn6gF8BjLzYkL0axbHuCA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/parser": "^7.29.0", + "hermes-parser": "0.36.1", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "tinyglobby": "^0.2.15", + "yargs": "^17.6.2" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/react-native/node_modules/@sinclair/typebox": { + "version": "0.27.12", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.12.tgz", + "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/react-native/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/react-native/node_modules/babel-plugin-syntax-hermes-parser": { + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.36.1.tgz", + "integrity": "sha512-ycduwJbvdvIMmVvlAZqGggS+pm5Eu4Bk9pcV9Sm2Z4PJNRVsKkv0g7vHj+LeuC1gHTeF67sJXFOq61IlqCa2hA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "hermes-parser": "0.36.1" + } + }, + "node_modules/react-native/node_modules/hermes-estree": { + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.36.1.tgz", + "integrity": "sha512-guv1nQ6IJ7S83NRFPWc3SA7IBZrdNC9kapwOq6uXvF4wP+sDCgjzQbKPCoyYmoyZRzztF/n/c36l/rccCZSiCw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/react-native/node_modules/hermes-parser": { + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.36.1.tgz", + "integrity": "sha512-GApNk4zLHi2UWoWZZkx7LNCOSzLSc5lB55pZ/PhK7ycFeg7u5LcF88p/WbpIi1XUDtE0MpHE3uRR3u3KB7TjSQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "hermes-estree": "0.36.1" + } + }, + "node_modules/react-native/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/react-native/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/react-native/node_modules/ws": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-test-renderer": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-19.2.8.tgz", + "integrity": "sha512-GHKPaDRaNYU24PHTLG8Bx8VMY9t+qNfxQbt/Yjp7aMWBkKU6766SR0n6TnYu7P5I1MfEuAMUadqiyDHyI4Yy9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "react-is": "^19.2.8", + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/react-test-renderer/node_modules/react-is": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/recast": { + "version": "0.23.21", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.21.tgz", + "integrity": "sha512-mFAyJq9vUbSTARLZUvAEf1z3YxlvAwswbmxMx2mPA/MSm4KmpwvwvhsH/NIrZhyOuwD60Lzyw2qh83uCbgTPYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/recast/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/recursive-copy": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/recursive-copy/-/recursive-copy-2.0.14.tgz", + "integrity": "sha512-K8WNY8f8naTpfbA+RaXmkaQuD1IeW9EgNEfyGxSqqTQukpVtoOKros9jUqbpEsSw59YOmpd8nCBgtqJZy5nvog==", + "dev": true, + "license": "ISC", + "dependencies": { + "errno": "^0.1.2", + "graceful-fs": "^4.1.4", + "junk": "^1.0.1", + "maximatch": "^0.1.0", + "mkdirp": "^0.5.1", + "pify": "^2.3.0", + "promise": "^7.0.1", + "rimraf": "^2.7.1", + "slash": "^1.0.0" + } + }, + "node_modules/recursive-copy/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/recursive-copy/node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/recursive-copy/node_modules/slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/regexp-to-ast": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", + "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==", + "dev": true, + "license": "MIT" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.2.tgz", + "integrity": "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "license": "Unlicense" + }, + "node_modules/rollup": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", + "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.62.4", + "@rollup/rollup-android-arm64": "4.62.4", + "@rollup/rollup-darwin-arm64": "4.62.4", + "@rollup/rollup-darwin-x64": "4.62.4", + "@rollup/rollup-freebsd-arm64": "4.62.4", + "@rollup/rollup-freebsd-x64": "4.62.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", + "@rollup/rollup-linux-arm-musleabihf": "4.62.4", + "@rollup/rollup-linux-arm64-gnu": "4.62.4", + "@rollup/rollup-linux-arm64-musl": "4.62.4", + "@rollup/rollup-linux-loong64-gnu": "4.62.4", + "@rollup/rollup-linux-loong64-musl": "4.62.4", + "@rollup/rollup-linux-ppc64-gnu": "4.62.4", + "@rollup/rollup-linux-ppc64-musl": "4.62.4", + "@rollup/rollup-linux-riscv64-gnu": "4.62.4", + "@rollup/rollup-linux-riscv64-musl": "4.62.4", + "@rollup/rollup-linux-s390x-gnu": "4.62.4", + "@rollup/rollup-linux-x64-gnu": "4.62.4", + "@rollup/rollup-linux-x64-musl": "4.62.4", + "@rollup/rollup-openbsd-x64": "4.62.4", + "@rollup/rollup-openharmony-arm64": "4.62.4", + "@rollup/rollup-win32-arm64-msvc": "4.62.4", + "@rollup/rollup-win32-ia32-msvc": "4.62.4", + "@rollup/rollup-win32-x64-gnu": "4.62.4", + "@rollup/rollup-win32-x64-msvc": "4.62.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-clear": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/rollup-plugin-clear/-/rollup-plugin-clear-2.0.7.tgz", + "integrity": "sha512-Hg8NC3JcJBO1ofgyQC0IACpyKn/yhHPGZ3C7R3ubNGWUXy9JXHQrewk4J4hVcZznw6SOKayLsaNae596Rwt8Vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "rimraf": "^2.6.2" + } + }, + "node_modules/rollup-plugin-command": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-command/-/rollup-plugin-command-1.1.3.tgz", + "integrity": "sha512-9nIcP5mgVYWGU7x/6ufTgtqI4vl5vvsYs6fTTil91NX53EIPcim42FXmq1TPdZRFJbUM1ikrg05clahPxObL1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup-plugin-license": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-license/-/rollup-plugin-license-3.7.1.tgz", + "integrity": "sha512-FcGXUbAmPvRSLxjVdjp/r/MUtKBlttVQd+ApUyvKfREnsoAfAZA6Ic2fE1Tz4RL0f9XqEQU9UIRNUMdtQtliDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "commenting": "^1.1.0", + "fdir": "^6.4.3", + "lodash": "^4.17.21", + "magic-string": "^0.30.0", + "moment": "^2.30.1", + "package-name-regex": "^2.0.6", + "spdx-expression-validate": "^2.0.0", + "spdx-satisfies": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/rollup-plugin-livereload": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/rollup-plugin-livereload/-/rollup-plugin-livereload-2.0.5.tgz", + "integrity": "sha512-vqQZ/UQowTW7VoiKEM5ouNW90wE5/GZLfdWuR0ELxyKOJUIaj+uismPZZaICU4DnWPVjnpCDDxEqwU7pcKY/PA==", + "dev": true, + "license": "MIT", + "dependencies": { + "livereload": "^0.9.1" + }, + "engines": { + "node": ">=8.3" + } + }, + "node_modules/rollup-plugin-postcss": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-postcss/-/rollup-plugin-postcss-4.0.2.tgz", + "integrity": "sha512-05EaY6zvZdmvPUDi3uCcAQoESDcYnv8ogJJQRp6V5kZ6J6P7uAVJlrTZcaaA20wTH527YTnKfkAoPxWI/jPp4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "concat-with-sourcemaps": "^1.1.0", + "cssnano": "^5.0.1", + "import-cwd": "^3.0.0", + "p-queue": "^6.6.2", + "pify": "^5.0.0", + "postcss-load-config": "^3.0.0", + "postcss-modules": "^4.0.0", + "promise.series": "^0.2.0", + "resolve": "^1.19.0", + "rollup-pluginutils": "^2.8.2", + "safe-identifier": "^0.4.2", + "style-inject": "^0.3.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "postcss": "8.x" + } + }, + "node_modules/rollup-plugin-postcss/node_modules/pify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", + "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rollup-plugin-re": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/rollup-plugin-re/-/rollup-plugin-re-1.0.7.tgz", + "integrity": "sha512-TyFf3QaV/eJ/50k4wp5BM0SodGy0Idq0uOgvA1q3gHRwgXLPVX5y3CRKkBuHzKTZPC9CTZX7igKw5UvgjDls8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.16.0", + "rollup-pluginutils": "^2.0.1" + } + }, + "node_modules/rollup-plugin-re/node_modules/magic-string": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.16.0.tgz", + "integrity": "sha512-c4BEos3y6G2qO0B9X7K0FVLOPT9uGrjYwYRLFmDqyl5YMboUviyecnXWp94fJTSMwPw2/sf+CEYt5AGpmklkkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "vlq": "^0.2.1" + } + }, + "node_modules/rollup-plugin-re/node_modules/vlq": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", + "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "estree-walker": "^0.6.1" + } + }, + "node_modules/rollup-pluginutils/node_modules/estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-identifier": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/safe-identifier/-/safe-identifier-0.4.2.tgz", + "integrity": "sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==", + "dev": true, + "license": "ISC" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sass": { + "version": "1.102.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.102.0.tgz", + "integrity": "sha512-NSOyTnaQF7rTAEOtI2fwb386vL+akyiQLBZu8Na7hXCb+umJy0GAqlcMIaqACZ6Z1VgTBS4K9PG6B3IdjHGJsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "immutable": "^5.1.5", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=20.19.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sass/node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/send/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/serialize-javascript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.1.0.tgz", + "integrity": "sha512-RNEqWOyhhUQYN9V1GfHwu9AR/g+NTciH6Z5u3/no6X3/w+04J2lVDL+svFQVXgXrEGBMG2puMVN3gq2SNGuTGw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", + "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shelljs": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.10.0.tgz", + "integrity": "sha512-Jex+xw5Mg2qMZL3qnzXIfaxEtBaC4n7xifqaqtrZDdlheR70OGkydrPJWT0V1cA1k3nanC86x9FwAmQl6w3Klw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "execa": "^5.1.1", + "fast-glob": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/shx": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.4.0.tgz", + "integrity": "sha512-Z0KixSIlGPpijKgcH6oCMCbltPImvaKy0sGH8AkLRXw1KyzpKtaCTizP2xen+hNDqVF4xxgvA0KXSb9o4Q6hnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.8", + "shelljs": "^0.9.2" + }, + "bin": { + "shx": "lib/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/shx/node_modules/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/shx/node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/shx/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/shx/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shx/node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/shx/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/shx/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/shx/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shx/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shx/node_modules/shelljs": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.9.2.tgz", + "integrity": "sha512-S3I64fEiKgTZzKCC46zT/Ib9meqofLrQVbpSswtjFfAVDW+AZ54WTnAM/3/yENoxz/V1Cy6u3kiiEbQ4DNphvw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "execa": "^1.0.0", + "fast-glob": "^3.3.2", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/shx/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/smob": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.6.2.tgz", + "integrity": "sha512-RQsvleCbF8cVHEv+xuDGaA4pOizFqJ0GgjtMSRo6oP8pnN7WsigHgVGey6aILRBKv4W2YOMHLqbKdnB6hpB9fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/spdx-compare/-/spdx-compare-1.0.0.tgz", + "integrity": "sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-find-index": "^1.0.2", + "spdx-expression-parse": "^3.0.0", + "spdx-ranges": "^2.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-expression-validate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-validate/-/spdx-expression-validate-2.0.0.tgz", + "integrity": "sha512-b3wydZLM+Tc6CFvaRDBOF9d76oGIHNCLYFeHbftFXUWjnfZWganmDmvtM5sm1cRwJc/VDBMLyGGrsLFd1vOxbg==", + "dev": true, + "license": "(MIT AND CC-BY-3.0)", + "dependencies": { + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/spdx-ranges": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/spdx-ranges/-/spdx-ranges-2.1.1.tgz", + "integrity": "sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA==", + "dev": true, + "license": "(MIT AND CC-BY-3.0)" + }, + "node_modules/spdx-satisfies": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/spdx-satisfies/-/spdx-satisfies-5.0.1.tgz", + "integrity": "sha512-Nwor6W6gzFp8XX4neaKQ7ChV4wmpSh2sSDemMFSzHxpTw460jxFYeOn+jq4ybnSSw/5sc3pjka9MQPouksQNpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-compare": "^1.0.0", + "spdx-expression-parse": "^3.0.0", + "spdx-ranges": "^2.0.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility", + "dev": true, + "license": "MIT" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/stacktrace-parser": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz", + "integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "type-fest": "^0.7.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stacktrace-parser/node_modules/type-fest": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", + "integrity": "sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-inject": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/style-inject/-/style-inject-0.3.0.tgz", + "integrity": "sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==", + "dev": true, + "license": "MIT" + }, + "node_modules/stylehacks": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", + "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svgo": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.3.tgz", + "integrity": "sha512-5EZD0pafXX6PphdwOGCiVLDSaV1xyuQao2blHajHLsPxr07q4mmEjdtXEWgG07ae2mIz8Ex2CDXNCTiXhy3Khw==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "sax": "^1.5.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/synckit": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.13.tgz", + "integrity": "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.3.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/terser": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.50.0.tgz", + "integrity": "sha512-CN9BVxWhgS/hRxtUMjtC2uRWSTcSfQFHMDWma6sKKfIivCD91sM+FOPfvwoaRMqCSrUpe1nv3jDamd9eEQ4y+w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/terser/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/topojson-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", + "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", + "license": "ISC", + "dependencies": { + "commander": "2" + }, + "bin": { + "topo2geo": "bin/topo2geo", + "topomerge": "bin/topomerge", + "topoquantize": "bin/topoquantize" + } + }, + "node_modules/topojson-client/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-jest": { + "version": "29.4.12", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.12.tgz", + "integrity": "sha512-Ov6ClY53Fflh6BGAnY2DlTq1hYDrTycz2PVTXBWFW2CU+9zrEqAp9fWdGXl42EXO5RLSFAcAZ2JFKbP+zBTFfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.9", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.8.5", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <7" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unrs-resolver": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.4" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", + "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz", + "integrity": "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/vega": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/vega/-/vega-6.3.1.tgz", + "integrity": "sha512-mX5tvY3ISCSiPPmunuZyQfccq0XlUvJd2t5oyc6SNS0N0TwnPNoklx0mVod5n+qbzuUoiuxl7Ve/SvoRqH4RzA==", + "license": "BSD-3-Clause", + "dependencies": { + "vega-crossfilter": "~5.1.2", + "vega-dataflow": "~6.1.2", + "vega-encode": "~5.2.1", + "vega-event-selector": "~4.0.0", + "vega-expression": "~6.2.1", + "vega-force": "~5.1.2", + "vega-format": "~2.1.2", + "vega-functions": "~6.1.3", + "vega-geo": "~5.1.2", + "vega-hierarchy": "~5.1.2", + "vega-label": "~2.1.2", + "vega-loader": "~5.1.2", + "vega-parser": "~7.1.2", + "vega-projection": "~2.1.2", + "vega-regression": "~2.1.2", + "vega-runtime": "~7.1.2", + "vega-scale": "~8.1.2", + "vega-scenegraph": "~5.2.1", + "vega-statistics": "~2.0.0", + "vega-time": "~3.2.1", + "vega-transforms": "~5.2.1", + "vega-typings": "~2.2.0", + "vega-util": "~2.1.0", + "vega-view": "~6.1.2", + "vega-view-transforms": "~5.2.1", + "vega-voronoi": "~5.1.2", + "vega-wordcloud": "~5.1.2" + }, + "funding": { + "url": "https://app.hubspot.com/payments/GyPC972GD9Rt" + } + }, + "node_modules/vega-canvas": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vega-canvas/-/vega-canvas-2.0.0.tgz", + "integrity": "sha512-9x+4TTw/USYST5nx4yN272sy9WcqSRjAR0tkQYZJ4cQIeon7uVsnohvoPQK1JZu7K1QXGUqzj08z0u/UegBVMA==", + "license": "BSD-3-Clause" + }, + "node_modules/vega-crossfilter": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vega-crossfilter/-/vega-crossfilter-5.1.3.tgz", + "integrity": "sha512-goGulwrrbmv9mY4Za8HQiiFk7WV1OTyrSVZzrMR0Keeiyzh9cnkxRGF1W2fAhMNA3HSaRaBoxWHGr7H9fvovuQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "vega-dataflow": "^6.1.3", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-dataflow": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/vega-dataflow/-/vega-dataflow-6.1.3.tgz", + "integrity": "sha512-ac51FLdYT8XAcDaHt3bQtVAR3UFdKfPMQBhjvBMXp69mdeq2ERfM1u9CUyDQAVZJylkIbWEwDnsp72tIwenPUA==", + "license": "BSD-3-Clause", + "dependencies": { + "vega-format": "^2.1.3", + "vega-loader": "^5.1.3", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-embed": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/vega-embed/-/vega-embed-7.1.0.tgz", + "integrity": "sha512-ZmEIn5XJrQt7fSh2lwtSdXG/9uf3yIqZnvXFEwBJRppiBgrEWZcZbj6VK3xn8sNTFQ+sQDXW5sl/6kmbAW3s5A==", + "license": "BSD-3-Clause", + "dependencies": { + "fast-json-patch": "^3.1.1", + "json-stringify-pretty-compact": "^4.0.0", + "semver": "^7.7.2", + "tslib": "^2.8.1", + "vega-interpreter": "^2.0.0", + "vega-schema-url-parser": "^3.0.2", + "vega-themes": "3.0.0", + "vega-tooltip": "1.0.0" + }, + "funding": { + "url": "https://app.hubspot.com/payments/GyPC972GD9Rt" + }, + "peerDependencies": { + "vega": "*", + "vega-lite": "*" + } + }, + "node_modules/vega-encode": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/vega-encode/-/vega-encode-5.2.2.tgz", + "integrity": "sha512-YmriG349VjJsIxXE1gkxHj6fPF87T2t/fYmAUUlGzTGarSy3KF93t8Fqc6EPupsgnCDEhe3+0NsSWTVxiOJ1Ow==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "d3-interpolate": "^3.0.1", + "vega-dataflow": "^6.1.3", + "vega-scale": "^8.1.3", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-event-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/vega-event-selector/-/vega-event-selector-4.0.0.tgz", + "integrity": "sha512-CcWF4m4KL/al1Oa5qSzZ5R776q8lRxCj3IafCHs5xipoEHrkgu1BWa7F/IH5HrDNXeIDnqOpSV1pFsAWRak4gQ==", + "license": "BSD-3-Clause" + }, + "node_modules/vega-expression": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/vega-expression/-/vega-expression-6.2.2.tgz", + "integrity": "sha512-9yTpQBYDnl4yC27iGbUxDUnRAeO+la/lfzA5WSALB0INvA2A2NVxIorBakfqIkO7nwKM9bT3rmNRfj8Z3hjb7g==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/estree": "^1.0.9", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-force": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vega-force/-/vega-force-5.1.3.tgz", + "integrity": "sha512-njBlnPeeMY0uz1Fbdqa5jnOsSUrUo2s3z/Eb4qo486hhrwNtNpmeCsN+4PBasoblf/4pOpbvKULzolZmG+R7QA==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-force": "^3.0.0", + "vega-dataflow": "^6.1.3", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-format": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/vega-format/-/vega-format-2.1.3.tgz", + "integrity": "sha512-VK0yh3BtK5MUoG7aAXuwb678r2wDdykAUFIKHd9MWvXNo857UqNzZfYixfyXi8xXBK2AV38R4IiQmNKUrXBMUA==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "d3-format": "^3.1.2", + "d3-time-format": "^4.1.0", + "vega-time": "^3.3.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-format/node_modules/vega-time": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/vega-time/-/vega-time-3.3.0.tgz", + "integrity": "sha512-bm9uMPrGIPQ52jD3Ltr6gUspogDtO0G8pEzLKvLySX84reeShHTH6jOd9YXwISNfuS4LT+cmPr9Ct6TMgvPMOA==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "d3-time": "^3.1.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-functions": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/vega-functions/-/vega-functions-6.1.3.tgz", + "integrity": "sha512-WOgWVxGg1T1tuICyylIppxXWXWEtl7P81Xnj0vQtoIn11VvQ0mrsBZr1aia4g8Sw0fEH1mUO7Dtj7v3o8BgBmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "d3-color": "^3.1.0", + "d3-geo": "^3.1.1", + "vega-dataflow": "^6.1.2", + "vega-expression": "^6.2.1", + "vega-scale": "^8.1.2", + "vega-scenegraph": "^5.2.1", + "vega-selections": "^6.1.4", + "vega-statistics": "^2.0.0", + "vega-time": "^3.2.1", + "vega-util": "^2.1.0" + } + }, + "node_modules/vega-geo": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vega-geo/-/vega-geo-5.1.3.tgz", + "integrity": "sha512-UfTVPV+O+7elFyUsw8FLjiDOJkF0VZanC5tHLaSSue5h+xLzG6SDKQ7xEBIQSaTBE8nLSBtHm45Qve0SFjILVQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "d3-color": "^3.1.0", + "d3-geo": "^3.1.1", + "vega-canvas": "^2.0.0", + "vega-dataflow": "^6.1.3", + "vega-projection": "^2.1.3", + "vega-statistics": "^2.0.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-hierarchy": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vega-hierarchy/-/vega-hierarchy-5.1.3.tgz", + "integrity": "sha512-bDWDNGuUA4WcOD5CMh9Mahs3CwKxSU3W1XAnDGCpAwfJVoWp7vlVMt1paoTczPFTXtXGxIa3vJu94xWGmI3/og==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-hierarchy": "^3.1.2", + "vega-dataflow": "^6.1.3", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-interpreter": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/vega-interpreter/-/vega-interpreter-2.3.2.tgz", + "integrity": "sha512-JDAoi3taFcCDLujZG84TNNUXdkAZ5WsSHssx8lWVYaxb9Slsjk7v7PtRIYXpSlUwLaKGRBqoJ9KDs36Z0eMEIw==", + "license": "BSD-3-Clause", + "dependencies": { + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-label": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/vega-label/-/vega-label-2.1.3.tgz", + "integrity": "sha512-UcaGgrVr2Gb0sUj4j3L5tZuL6GOn7tyauYsfXsS0hURoIbHCfQI4SnRq89vl5ub5DL1SEWaJTKag4GdAm1hcGw==", + "license": "BSD-3-Clause", + "dependencies": { + "vega-canvas": "^2.0.0", + "vega-dataflow": "^6.1.3", + "vega-scenegraph": "^5.3.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-label/node_modules/vega-scenegraph": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/vega-scenegraph/-/vega-scenegraph-5.3.0.tgz", + "integrity": "sha512-sJbrDxGhyw8KFgC8NIEPBsZBZaOHAO4YtFcjw71bqIwVy6kIHlzc5I+buvJkEcBKkSTbsIdKRT1jUOHPDbBEKw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "^3.1.0", + "d3-shape": "^3.2.0", + "vega-canvas": "^2.0.0", + "vega-loader": "^5.1.3", + "vega-scale": "^8.1.3", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-lite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vega-lite/-/vega-lite-6.4.3.tgz", + "integrity": "sha512-d/7hPjfz560UERaQuTmGgIVfXAe3g2hJWeC+igDeaGohUdEoNrHLXgR/yTOBT8vV/lIuuKnw+0/xWWblkDwkMQ==", + "license": "BSD-3-Clause", + "dependencies": { + "json-stringify-pretty-compact": "~4.0.0", + "tslib": "~2.8.1", + "vega-event-selector": "~4.0.0", + "vega-expression": "~6.1.0", + "vega-util": "~2.1.0", + "yargs": "~18.0.0" + }, + "bin": { + "vl2pdf": "bin/vl2pdf", + "vl2png": "bin/vl2png", + "vl2svg": "bin/vl2svg", + "vl2vg": "bin/vl2vg" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://app.hubspot.com/payments/GyPC972GD9Rt" + }, + "peerDependencies": { + "vega": "^6.0.0" + } + }, + "node_modules/vega-lite/node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/vega-lite/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/vega-lite/node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/vega-lite/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/vega-lite/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vega-lite/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/vega-lite/node_modules/vega-expression": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/vega-expression/-/vega-expression-6.1.0.tgz", + "integrity": "sha512-hHgNx/fQ1Vn1u6vHSamH7lRMsOa/yQeHGGcWVmh8fZafLdwdhCM91kZD9p7+AleNpgwiwzfGogtpATFaMmDFYg==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/estree": "^1.0.8", + "vega-util": "^2.1.0" + } + }, + "node_modules/vega-lite/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/vega-lite/node_modules/yargs": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/vega-lite/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/vega-loader": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vega-loader/-/vega-loader-5.1.3.tgz", + "integrity": "sha512-gJGoI262B5EEUGRFRY2mH+SJbVc+b4MTQfASNYpyIBqGxHqmjvNQqTyB+7bjReRYJjExjXL/MnKJmJHL6dAOwA==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-dsv": "^3.0.1", + "topojson-client": "^3.1.0", + "vega-format": "^2.1.3", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-parser": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/vega-parser/-/vega-parser-7.1.3.tgz", + "integrity": "sha512-bT0pmzPF79ECFilKotgo92OUi25MGGgrEj9M0piiIpJZXQwcF2xsCf+5YRND1R0zd4zodNpqpJtIw0sl6TaqMg==", + "license": "BSD-3-Clause", + "dependencies": { + "vega-dataflow": "^6.1.3", + "vega-event-selector": "^4.0.0", + "vega-functions": "^6.2.0", + "vega-scale": "^8.1.3", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-parser/node_modules/vega-functions": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/vega-functions/-/vega-functions-6.2.0.tgz", + "integrity": "sha512-MRFL7RjVmsv6iYuRZVvrDPZbySLa5Dzd5hC+0LTubysy8OrPengKZEHeRnibvHH6odg3IsKUV44Wv8dN3XzYAw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "d3-color": "^3.1.0", + "d3-ease": "^3.0.1", + "d3-geo": "^3.1.1", + "vega-dataflow": "^6.1.3", + "vega-expression": "^6.2.2", + "vega-scale": "^8.1.3", + "vega-scenegraph": "^5.3.0", + "vega-selections": "^6.1.5", + "vega-statistics": "^2.0.0", + "vega-time": "^3.3.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-parser/node_modules/vega-scenegraph": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/vega-scenegraph/-/vega-scenegraph-5.3.0.tgz", + "integrity": "sha512-sJbrDxGhyw8KFgC8NIEPBsZBZaOHAO4YtFcjw71bqIwVy6kIHlzc5I+buvJkEcBKkSTbsIdKRT1jUOHPDbBEKw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "^3.1.0", + "d3-shape": "^3.2.0", + "vega-canvas": "^2.0.0", + "vega-loader": "^5.1.3", + "vega-scale": "^8.1.3", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-parser/node_modules/vega-time": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/vega-time/-/vega-time-3.3.0.tgz", + "integrity": "sha512-bm9uMPrGIPQ52jD3Ltr6gUspogDtO0G8pEzLKvLySX84reeShHTH6jOd9YXwISNfuS4LT+cmPr9Ct6TMgvPMOA==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "d3-time": "^3.1.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-projection": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/vega-projection/-/vega-projection-2.1.3.tgz", + "integrity": "sha512-IYGBnT+8a3ZH2bOM48c2qSZAnuPyyYTua/kMeXjwxFKI1/BjzlchQ9QCKxGeBqJGdG2pp9YMW2oS/F1dw/IQXw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-geo": "^3.1.1", + "d3-geo-projection": "^4.0.0", + "vega-scale": "^8.1.3" + } + }, + "node_modules/vega-regression": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/vega-regression/-/vega-regression-2.1.3.tgz", + "integrity": "sha512-qssCjc6KEV8pUQEEliZSTRv4cuhl5D2MgyfbKnIYbJM7B/lW/vubCPBr8oGzxVEGJd3D5kxEtXJORPipawdVEA==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "vega-dataflow": "^6.1.3", + "vega-statistics": "^2.0.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-runtime": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/vega-runtime/-/vega-runtime-7.1.3.tgz", + "integrity": "sha512-27id9NGfnGh0u/NpQMagAmS5wDa7ELKDv1SYysiJp05HOYPuLD9XT3NSblPjZhDhyk1eApzWJAoegUoosJJtkA==", + "license": "BSD-3-Clause", + "dependencies": { + "vega-dataflow": "^6.1.3", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-scale": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/vega-scale/-/vega-scale-8.1.3.tgz", + "integrity": "sha512-6Tx/1XMz2EtjOZm2zEONqJfYGfUWwSauhEyMCH5XWpnqoGjHQfDzDCFtBUFDs/6N+nNi3ldxCshraMiC4XCXLg==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-scale-chromatic": "^3.1.0", + "vega-time": "^3.3.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-scale/node_modules/vega-time": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/vega-time/-/vega-time-3.3.0.tgz", + "integrity": "sha512-bm9uMPrGIPQ52jD3Ltr6gUspogDtO0G8pEzLKvLySX84reeShHTH6jOd9YXwISNfuS4LT+cmPr9Ct6TMgvPMOA==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "d3-time": "^3.1.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-scenegraph": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/vega-scenegraph/-/vega-scenegraph-5.2.1.tgz", + "integrity": "sha512-tWolI3s/cjU5g8evEjpxJqgqKQ2IJv1FoPXqvNuviLNdylP1I/h5iF5RY08Cz4jkuP9zircSskFEBsHylD1dgQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "^3.1.0", + "d3-shape": "^3.2.0", + "vega-canvas": "^2.0.0", + "vega-loader": "^5.1.2", + "vega-scale": "^8.1.2", + "vega-util": "^2.1.0" + } + }, + "node_modules/vega-schema-url-parser": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/vega-schema-url-parser/-/vega-schema-url-parser-3.0.2.tgz", + "integrity": "sha512-xAnR7KAvNPYewI3O0l5QGdT8Tv0+GCZQjqfP39cW/hbe/b3aYMAQ39vm8O2wfXUHzm04xTe7nolcsx8WQNVLRQ==", + "license": "BSD-3-Clause" + }, + "node_modules/vega-selections": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/vega-selections/-/vega-selections-6.1.5.tgz", + "integrity": "sha512-evYoCV1wuE0kuiDrKH2dVVweMUuz0pNN7rYTYKBSNDqa7TBjDU4+tbWFm/CgowoaAqjC6Omd+jEZz9lwpgeE3w==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "3.2.4", + "vega-expression": "^6.2.2", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-statistics": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vega-statistics/-/vega-statistics-2.0.0.tgz", + "integrity": "sha512-dGPfDXnBlgXbZF3oxtkb8JfeRXd5TYHx25Z/tIoaa9jWua4Vf/AoW2wwh8J1qmMy8J03/29aowkp1yk4DOPazQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4" + } + }, + "node_modules/vega-themes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vega-themes/-/vega-themes-3.0.0.tgz", + "integrity": "sha512-1iFiI3BNmW9FrsLnDLx0ZKEddsCitRY3XmUAwp6qmp+p+IXyJYc9pfjlVj9E6KXBPfm4cQyU++s0smKNiWzO4g==", + "license": "BSD-3-Clause", + "funding": { + "url": "https://app.hubspot.com/payments/GyPC972GD9Rt" + }, + "peerDependencies": { + "vega": "*", + "vega-lite": "*" + } + }, + "node_modules/vega-time": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/vega-time/-/vega-time-3.2.1.tgz", + "integrity": "sha512-SXo1klLYPsmBdSj9itYSuFMmCpwnnIvQS8jWFxsCTnyWcPaBQKz0kf2M7DqTysi34KTk+6ws4kqZg11XMWnIYA==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "d3-time": "^3.1.0", + "vega-util": "^2.1.0" + } + }, + "node_modules/vega-tooltip": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vega-tooltip/-/vega-tooltip-1.0.0.tgz", + "integrity": "sha512-P1R0JP29v0qnTuwzCQ0SPJlkjAzr6qeyj+H4VgUFSykHmHc1OBxda//XBaFDl/bZgIscEMvjKSjZpXd84x3aZQ==", + "license": "BSD-3-Clause", + "dependencies": { + "vega-util": "^2.0.0" + }, + "funding": { + "url": "https://app.hubspot.com/payments/GyPC972GD9Rt" + } + }, + "node_modules/vega-transforms": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/vega-transforms/-/vega-transforms-5.2.2.tgz", + "integrity": "sha512-0vlu/36sBbHKw4dN/7BKCUPOzXc2oYObRxGMcROFLk+BwfjE/cqtfuXbLHzOinXtfLOhblmT0Agx+BSYXN8k/Q==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "vega-dataflow": "^6.1.3", + "vega-statistics": "^2.0.0", + "vega-time": "^3.3.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-transforms/node_modules/vega-time": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/vega-time/-/vega-time-3.3.0.tgz", + "integrity": "sha512-bm9uMPrGIPQ52jD3Ltr6gUspogDtO0G8pEzLKvLySX84reeShHTH6jOd9YXwISNfuS4LT+cmPr9Ct6TMgvPMOA==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "d3-time": "^3.1.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-typings": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vega-typings/-/vega-typings-2.2.0.tgz", + "integrity": "sha512-pX7LsqgMpzMPOyydg/drYbIjh+mmvLfKtuKr75OpCu/ZYKJ2i8UjSmNlpARYeYGLaXfuNUAM3hMN8NPZPDYiBQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/geojson": "7946.0.16", + "vega-event-selector": "^4.0.0", + "vega-expression": "^6.2.0", + "vega-util": "^2.1.0" + } + }, + "node_modules/vega-util": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/vega-util/-/vega-util-2.1.3.tgz", + "integrity": "sha512-Znj01Gj5XVUw/U7QvwjMhl+XCGs09UF5KDyMXZz9NtjhoKP04anWjcbA7hqoVH+eEigO92KlTi+Yai6Roo1n0A==", + "license": "BSD-3-Clause" + }, + "node_modules/vega-view": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/vega-view/-/vega-view-6.1.2.tgz", + "integrity": "sha512-BG/Kqd1csdK4uKdJ4hv72msgQX3Ry5SzCV1ngbytkn6QwuQPpFWzya/f0OSWyMQP9S79avdHXqdLCxjSeiDQRw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "^3.2.4", + "d3-timer": "^3.0.1", + "vega-dataflow": "^6.1.2", + "vega-format": "^2.1.2", + "vega-functions": "^6.1.3", + "vega-runtime": "^7.1.2", + "vega-scenegraph": "^5.2.1", + "vega-util": "^2.1.0" + } + }, + "node_modules/vega-view-transforms": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/vega-view-transforms/-/vega-view-transforms-5.2.2.tgz", + "integrity": "sha512-Zsfqy0AzCStVSEoS2lf416heWQp0lQpg9LxJhqLJVZLpTh9UzcTdxP5hMwN1StrB2gWCk7TEfBgJC9FBBzxiZA==", + "license": "BSD-3-Clause", + "dependencies": { + "vega-dataflow": "^6.1.3", + "vega-scenegraph": "^5.3.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-view-transforms/node_modules/vega-scenegraph": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/vega-scenegraph/-/vega-scenegraph-5.3.0.tgz", + "integrity": "sha512-sJbrDxGhyw8KFgC8NIEPBsZBZaOHAO4YtFcjw71bqIwVy6kIHlzc5I+buvJkEcBKkSTbsIdKRT1jUOHPDbBEKw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "^3.1.0", + "d3-shape": "^3.2.0", + "vega-canvas": "^2.0.0", + "vega-loader": "^5.1.3", + "vega-scale": "^8.1.3", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-voronoi": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vega-voronoi/-/vega-voronoi-5.1.3.tgz", + "integrity": "sha512-1EYf2KFE/otZmjyQSw2xisnT6QDXouyofHBI9o9C1zOfNM284+wmJ0qbvL46wX9jEauH+RKG5qnx0eDWQHVjYw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-delaunay": "^6.0.4", + "vega-dataflow": "^6.1.3", + "vega-util": "^2.1.3" + } + }, + "node_modules/vega-wordcloud": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vega-wordcloud/-/vega-wordcloud-5.1.3.tgz", + "integrity": "sha512-DxghqU1U9VPSKWmvatyQiDNShz1XfKv/XnIGlRWYdfTMnZvD3Y46mdrxPBhqFMF6TQKjZKyeqz5GtukA/RaH5Q==", + "license": "BSD-3-Clause", + "dependencies": { + "vega-canvas": "^2.0.0", + "vega-dataflow": "^6.1.3", + "vega-scale": "^8.1.3", + "vega-statistics": "^2.0.0", + "vega-util": "^2.1.3" + } + }, + "node_modules/vlq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", + "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "dev": true, + "license": "MIT" + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/xxhashjs": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", + "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cuint": "^0.2.2" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-a-folder": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/zip-a-folder/-/zip-a-folder-6.1.4.tgz", + "integrity": "sha512-6zRF/xi0zRxKOCTBVWLwWyyp+zuAKyBhM3Q2ddgEg8uvjDFSTsKulc476jUAaNXnw3lI8pGRuffRNPbxNUld+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "lzma": "^2.3.2", + "tinyglobby": "^0.2.17" + }, + "bin": { + "zip-a-folder": "dist/cli.mjs" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/cmd/mxcli/skillpacks_test.go b/cmd/mxcli/skillpacks_test.go index 3d4e20c2b..ab5583116 100644 --- a/cmd/mxcli/skillpacks_test.go +++ b/cmd/mxcli/skillpacks_test.go @@ -4,6 +4,7 @@ package main import ( "io/fs" + "path/filepath" "regexp" "strings" "testing" @@ -106,3 +107,65 @@ func TestVendoredPacksCarryNoForeignNamespace(t *testing.T) { t.Fatal(err) } } + +// TestWidgetPacksShipALockfile — a pack telling the reader to run `npm ci` must +// ship the lockfile that command requires. +// +// This is not hypothetical tidiness: mendix-vega-charts shipped documenting +// `npm ci` with no lock, so the one command the pack told people to run failed +// on the spot ("can only install with an existing package-lock.json"). It +// survived an end-to-end verification because that run used `npm install` — +// proving the build worked while never exercising the documented path. +// +// The check is anchored on package.json rather than on the prose: a pack that +// builds JavaScript wants a reproducible tree whatever its docs happen to say. +// Direct dependencies being exact-pinned is not enough, because the transitive +// tree is not, and that drift surfaces as a compile error in somebody else's +// project long after anyone chose an upgrade. +func TestWidgetPacksShipALockfile(t *testing.T) { + fsys, err := packsFS() + if err != nil { + t.Fatalf("packsFS: %v", err) + } + err = fs.WalkDir(fsys, ".", func(p string, d fs.DirEntry, err error) error { + if err != nil || d.IsDir() || d.Name() != "package.json" { + return err + } + // Only the widget's own manifest; a scripts/ helper is not a build. + if !strings.Contains(p, "/widget/") { + return nil + } + lock := strings.TrimSuffix(p, "package.json") + "package-lock.json" + if _, err := fs.Stat(fsys, lock); err != nil { + t.Errorf("%s ships no %s; `npm ci` cannot run and the build is not reproducible", + p, filepath.Base(lock)) + } + return nil + }) + if err != nil { + t.Fatal(err) + } +} + +// TestLockfilesAreNotRewritten — a lockfile must never be listed under +// rewrite.files. Substitution is what keeps a widget id unique, but a lock +// records resolved integrity hashes: rewriting one silently invalidates them +// and `npm ci` fails on a checksum, which reads as a corrupt registry rather +// than as a packaging mistake. +func TestLockfilesAreNotRewritten(t *testing.T) { + fsys, err := packsFS() + if err != nil { + t.Fatalf("packsFS: %v", err) + } + packs, err := skillpack.List(fsys) + if err != nil { + t.Fatalf("List: %v", err) + } + for _, p := range packs { + for _, f := range p.Rewrite.Files { + if strings.HasSuffix(f, "package-lock.json") { + t.Errorf("%s: %s is listed under rewrite.files; a lockfile must be shipped verbatim", p.Name, f) + } + } + } +} From 11b7f4ed6709cf8bc759feb6d46213c3c0463340 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 18:16:10 +0000 Subject: [PATCH 11/22] Let a pack place Java, and land the OData pushdown pack MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From the formula1 project's findings §54, which drafted and validated the pack and identified the one target the mechanism was missing. MDL cannot author a standalone class: createJavaActionStatement accepts a method body, with no class declaration and no imports clause. So a pack whose Java actions delegate into helper classes — this one is four two-line delegations into 882 lines of parser — could ship only prose telling somebody to copy a directory by hand. That is precisely the manual step packs exist to remove, which is why it was worth a new target rather than a workaround. `installs.java` is the third target and the only one writing outside .claude/skills//, because a helper class compiles only where the module expects it. A Java package is a class's identity exactly as a widget id is — two projects sharing one are two projects claiming the same class — so it reuses the substitution machinery unchanged: {{MODULE}} and {{MODULE_PATH}}, declared in rewrite.files, supplied by --module. Three rules, each with a wrong default available: - java/actions/ is NOT placed. mxcli writes those classes from the MDL, so placing the pack's copies means two sources of truth for the same files and applying the MDL overwrites them immediately. - An existing file that differs is refused, never overwritten (guard-don't-drop, ADR-0005). A locally fixed helper and a stale copy are indistinguishable from here; silently replacing somebody's edited parser is not a trade to make for them. The refusal names the files. - NeedsNamespace now keys on installs.widgets rather than on rewrite.files. This pack tokenises eight files and wants a MODULE, never a NAMESPACE, and asking the wrong question invites an answer that goes nowhere. make check-skill-mdl now substitutes before checking, because that is the only form anyone runs. Checking the raw file fails on every tokenised pack — it did, on this one — and whoever hit that would be tempted to drop the check rather than fix it. Confirmed still able to fail: a deliberately broken statement in the tokenised MDL is caught. Verified end to end rather than at the unit level: skill add mendix-odata-pushdown --module ODataPushdown -> 3 helpers in javasource/odatapushdown/, 4 action classes excluded -> package odatapushdown; in all three, MDL naming ODataPushdown.*, action bodies delegating to odatapushdown.* mxcli check on the substituted MDL -> 8 statements, syntax OK javac on the placed 633-line parser -> compiles clean re-run -> nothing written edit a helper, re-run -> refused by name, edit intact The last two matter most: the placement is idempotent, and it will not eat your changes. Also fixed a message that would have misled: the "MDL uses a MyModule placeholder" hint is printed only for a pack whose MDL was NOT substituted. Saying it of one that just had its real module name written in sends the reader hunting for a placeholder that is not there. references/packaging-gap.md is kept rather than deleted, reframed as the reasoning behind the target's shape — the next pack wanting a new target needs the same argument made. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4 --- .../packs/mendix-odata-pushdown/SKILL.md | 142 ++++ .../java/ODataQueryParser.java | 633 ++++++++++++++++++ .../java/QueryObject.java | 64 ++ .../java/RoutineCall.java | 185 +++++ .../java/actions/CallStatement.java | 59 ++ .../java/actions/FilterNumber.java | 56 ++ .../java/actions/Key.java | 53 ++ .../java/actions/Parse.java | 73 ++ .../mendix-odata-pushdown/mdl/module.mdl | 274 ++++++++ .../packs/mendix-odata-pushdown/pack.yaml | 68 ++ .../references/failure-modes.md | 112 ++++ .../references/packaging-gap.md | 123 ++++ .../references/patterns.md | 127 ++++ Makefile | 11 +- cmd/mxcli/cmd_skill.go | 80 ++- cmd/mxcli/skillpack/java.go | 165 +++++ cmd/mxcli/skillpack/java_test.go | 172 +++++ cmd/mxcli/skillpack/skillpack.go | 28 +- cmd/mxcli/skillpacks_test.go | 5 + docs/11-proposals/PROPOSAL_skill_packs.md | 30 + 20 files changed, 2452 insertions(+), 8 deletions(-) create mode 100644 .claude/skills/packs/mendix-odata-pushdown/SKILL.md create mode 100644 .claude/skills/packs/mendix-odata-pushdown/java/ODataQueryParser.java create mode 100644 .claude/skills/packs/mendix-odata-pushdown/java/QueryObject.java create mode 100644 .claude/skills/packs/mendix-odata-pushdown/java/RoutineCall.java create mode 100644 .claude/skills/packs/mendix-odata-pushdown/java/actions/CallStatement.java create mode 100644 .claude/skills/packs/mendix-odata-pushdown/java/actions/FilterNumber.java create mode 100644 .claude/skills/packs/mendix-odata-pushdown/java/actions/Key.java create mode 100644 .claude/skills/packs/mendix-odata-pushdown/java/actions/Parse.java create mode 100644 .claude/skills/packs/mendix-odata-pushdown/mdl/module.mdl create mode 100644 .claude/skills/packs/mendix-odata-pushdown/pack.yaml create mode 100644 .claude/skills/packs/mendix-odata-pushdown/references/failure-modes.md create mode 100644 .claude/skills/packs/mendix-odata-pushdown/references/packaging-gap.md create mode 100644 .claude/skills/packs/mendix-odata-pushdown/references/patterns.md create mode 100644 cmd/mxcli/skillpack/java.go create mode 100644 cmd/mxcli/skillpack/java_test.go diff --git a/.claude/skills/packs/mendix-odata-pushdown/SKILL.md b/.claude/skills/packs/mendix-odata-pushdown/SKILL.md new file mode 100644 index 000000000..8cd7f8ac6 --- /dev/null +++ b/.claude/skills/packs/mendix-odata-pushdown/SKILL.md @@ -0,0 +1,142 @@ +--- +name: mendix-odata-pushdown +description: Push OData query options into the SQL of a Mendix resource served by a read microflow, so $filter, $orderby, $top, $skip, $count and the key lookup reach the database instead of being silently dropped. Use when publishing an OData resource over data Mendix has no table for — a warehouse view, a legacy database, a Databricks catalogue, a stored procedure, a CSV read through a connector — or when a published resource returns 200 with the wrong rows and nothing appears in the log. +--- + +# Pushing OData query options into your own SQL + +## The problem this exists for + +Mendix will publish any entity over OData, including one with no table behind +it: declare the resource non-persistable, give it a read microflow, done. What +the documentation does not say is that Mendix then applies **none** of the query +options to your answer. `$filter`, `$orderby`, `$top`, `$skip`, `$count` and the +key lookup all arrive on the request URI and all stay there. Whatever the +microflow returns is exactly what the client gets. + +That is not a 500 and not an empty grid. It is a **200 with the wrong rows**: + +- `?$top=5` returns all 917 rows and the widget shows five of them, so paging + looks correct while every page ships the whole table. +- A client re-reading one held row by key gets the collection back, adopts the + first row as that object's identity, and **keeps it**. A detail page for one + record shows another record's data, and nothing logs a word. + +The second one is the reason to care. It is not a performance problem; it is +wrong data on screen, and every check is green. + +## What the pack gives you + +Four Java actions and the entity they return. Nothing in them is specific to any +database or any app. + +``` +{{MODULE}}.Parse(Uri, Columns, Dialect, MaxTop, DefaultTop, + DefaultOrderBy, KeyField, RejectUnsupported) -> {{MODULE}}.Query +{{MODULE}}.Key(Uri, KeyField) -> String +{{MODULE}}.FilterNumber(Uri, Field, Fallback) -> Long +{{MODULE}}.CallStatement(Routine, Kind, Parameters, Dialect) -> String +``` + +`Parse` is the whole thing. `Key` and `FilterNumber` are short forms for the +common case — a resource reachable one way only ("the sessions of this +weekend"), whose entire contract is one value out of `$filter`. `CallStatement` +builds the invocation for a resource backed by a stored routine. + +### `{{MODULE}}.Query` + +| Field | Style | What it is | +|---|---|---| +| `FilterSql` | splice | `" WHERE …"`, or empty | +| `OrderBySql` | splice | `" ORDER BY … LIMIT n OFFSET m"` | +| `Key` | bind | the key the client is re-reading one row by; empty for a collection | +| `Top`, `Skip` | bind | the page, already clamped to `MaxTop` | +| `SortColumn1/2`, `SortDirection1/2` | bind | the sort, as exposed names and `A`/`D` | +| `WantsCount` | both | `$count=true` — the client wants the size of the set | +| `Rejected`, `RejectReason` | both | the request asked for something untranslatable | + +## Two ways to spend it + +Which one you get is decided by whether you own the SQL. + +**Splice** — you build the statement, so concatenate the fragments into it. + +``` +$Q = CALL JAVA ACTION {{MODULE}}.Parse( + Uri = $Request/Uri, Columns = $Cols, Dialect = 'postgresql', + MaxTop = 500, DefaultTop = 500, DefaultOrderBy = 'name ASC', + KeyField = 'driverId', RejectUnsupported = true); + +DECLARE $Sql String = $Select + $Q/FilterSql + $Q/OrderBySql; +``` + +**Bind** — the SQL lives somewhere you cannot rewrite: a named query on a +database connection, a view, a procedure. Take the values, pass them as +parameters. This style is why the module is not simply a SQL builder: most data +worth publishing this way sits behind SQL somebody else owns. + +`references/patterns.md` has the working shape of both, including the `CASE` +construction that makes a fixed statement sortable by a parameter. + +## `Columns` — the whitelist, and why the type is not decoration + +`exposedName:sqlExpression:type`, comma-separated: + +``` +'name:d.name:text,wins:d.race_wins:number,active:d.is_active:bool,born:d.dob:date' +``` + +Nothing outside this list can be filtered or sorted on, and a filter naming +something outside it is a **rejection**, not an omission. + +Mendix quotes a literal according to what the *widget* believes the attribute +is, which is not always what the column is: a combo box on a numeric key sends +`year eq '1957'` while the grid header above it sends `year eq 1957`. Passing +the quotes through gives the engine `year = '1957'` against a BIGINT — zero +rows, status 200. The type is what makes both spellings mean the same thing. + +## What it understands + +Everything Mendix's OData client emits, and nothing else. + +| | | +|---|---| +| comparisons | `eq` `ne` `gt` `ge` `lt` `le` | +| functions | `contains` `startswith` `endswith` | +| logic | `and` `or` `not`, parentheses, correct precedence | +| literals | text, numbers, decimals, `true`/`false`, `null`, ISO instants | +| options | `$filter` `$orderby` (two terms) `$top` `$skip` `$count` | +| the key | `?$filter=k eq 'v'`, `/Res('v')`, `/Res(k='v')` | +| dialects | `postgresql` `duckdb` `sqlserver` `oracle` `mysql` | + +OData itself is far larger — arithmetic, lambdas, `$apply`, date functions, +`any`/`all`. None of it is emitted by a Mendix client, so none of it is here. A +request outside the grammar is **rejected**, not ignored: `Rejected` comes back +true and the caller is expected to fail the request. Dropping a filter you could +not read returns more rows than were asked for and calls it success, which is +the bug the module exists to stop. + +## Install + +```bash +mxcli skill add mendix-odata-pushdown --apply -p App.mpr +``` + +Then add `{{MODULE}}.User` to whichever user roles your published service runs +as — the pack cannot do that itself without knowing your role names. + +**`--apply` writes to the model**: a module, the `Query` entity, a module role +and four Java actions. Without it the pack only copies its own files. + +> **Not installable yet.** `installs.java` is a proposed manifest target that +> mxcli does not implement, and without it the MDL applies but the helper +> classes it delegates to never reach `javasource/`. `references/packaging-gap.md` +> has the detail and the manual fallback. + +## Read next + +| File | For | +|---|---| +| `references/patterns.md` | splice and bind end to end, and the sortable-fixed-statement `CASE` | +| `references/failure-modes.md` | what breaks, symptom first | +| `references/packaging-gap.md` | why this pack does not install, and how to apply it by hand | diff --git a/.claude/skills/packs/mendix-odata-pushdown/java/ODataQueryParser.java b/.claude/skills/packs/mendix-odata-pushdown/java/ODataQueryParser.java new file mode 100644 index 000000000..795991d0e --- /dev/null +++ b/.claude/skills/packs/mendix-odata-pushdown/java/ODataQueryParser.java @@ -0,0 +1,633 @@ +package {{MODULE_PATH}}; + +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Turns the OData query options on a request URI into SQL, for a resource whose + * rows come from somewhere Mendix cannot query itself. + * + *

Why this exists. A published entity backed by a read microflow gets + * handed the request and returns a list; Mendix applies none of {@code $filter}, + * {@code $orderby}, {@code $top}, {@code $skip} or the key lookup to it. Every + * one of those is the microflow's job, and a microflow that does not do the job + * does not fail — it answers the wrong question with a 200. So this parses the + * request once and hands back everything needed to answer it properly. + * + *

The grammar is not a guess. It is what Mendix's own OData client + * emits, captured off the wire from a running app driving real datagrids (see + * FINDINGS §45): + * + *

+ *   name eq 'Ayrton Senna'          (raceWins gt 10) and (podiums gt 20)
+ *   name ne 'Ayrton Senna'          (name eq 'a') or (name eq 'b')
+ *   raceWins gt|ge|lt|le 40         contains(name,'Sen')
+ *   points gt 100.5                 startswith(name,'Ayr') / endswith(name,'nna')
+ *   nationality eq null             championshipWon eq true
+ * 
+ * + * Covering exactly that set is what makes a widget sitting on an external entity + * work rather than half-work. + * + *

Two ways to use the result. Callers that build their own statement + * take {@link Result#filterSql} and {@link Result#orderBySql} and splice them + * in. Callers whose SQL lives elsewhere — a view, a stored procedure, a query + * defined on the database connection — take {@link Result#key}, + * {@link Result#top}, {@link Result#skip} and the sort terms and bind them. + * Same parse, and the second kind is the reason this is not just a SQL builder: + * you usually cannot rewrite someone else's warehouse SQL. + * + *

Injection. Column names come from the client and are resolved + * through a whitelist the caller supplies; nothing else reaches the SQL. A name + * absent from the whitelist is a rejection, not an omission, because a dropped + * filter silently returns more rows than were asked for. Literals are escaped, + * and anything numeric must parse as a number. + * + *

No Mendix here. This class is plain Java over strings so it can be + * exercised without a runtime. {@link QueryObject} is the binding. + */ +public final class ODataQueryParser { + + private ODataQueryParser() { + } + + // ---------------------------------------------------------------- types + + /** What a caller needs to answer the request, in both styles. */ + public static final class Result { + /** " WHERE …", or "" when the client asked for everything. */ + public String filterSql = ""; + /** " ORDER BY … LIMIT n OFFSET m" for callers that splice. */ + public String orderBySql = ""; + /** The key a client is re-reading a held row by; "" for a collection. */ + public String key = ""; + public long top; + public long skip; + public boolean wantsCount; + /** First two sort terms, as exposed names, for callers that bind. */ + public String sortColumn1 = ""; + public String sortDirection1 = "A"; + public String sortColumn2 = ""; + public String sortDirection2 = "A"; + /** True when the request asked for something untranslatable. */ + public boolean rejected; + public String rejectReason = ""; + } + + /** + * One whitelisted column: what the client calls it, the SQL behind it, and + * its type. + * + *

The type is not decoration. Mendix quotes a literal according to what + * the widget thinks the attribute is, which is not always what the + * column is: a combo box bound to a numeric key sends {@code year eq '1957'} + * while the grid header on the same column sends {@code year eq 1957}. A + * comparison that pastes the quotes through gives DuckDB + * {@code year = '1957'} against a BIGINT, which is zero rows and a 200. + * FINDINGS §42. + */ + private static final class Column { + static final String TEXT = "text"; + static final String NUMBER = "number"; + static final String BOOL = "bool"; + static final String DATE = "date"; + + final String sql; + final String type; + + Column(String sql, String type) { + this.sql = sql; + this.type = type; + } + } + + // ------------------------------------------------------------- entry point + + /** + * @param uri the request URI, query string and all + * @param columnMap whitelist, {@code exposed:sqlExpression:type,…}; the + * type may be omitted and defaults to text + * @param dialect postgresql | duckdb | sqlserver | oracle | mysql + * @param maxTop the largest page this resource will serve + * @param defaultTop what to use when the client asks for no page at all + * @param defaultOrderBy SQL appended when the client asks for no order + * @param keyField the exposed name of the resource's key + */ + public static Result parse(String uri, String columnMap, String dialect, + long maxTop, long defaultTop, String defaultOrderBy, + String keyField) { + Result r = new Result(); + Map cols = parseColumnMap(columnMap); + Map opts = parseQuery(uri); + Dialect d = Dialect.of(dialect); + + r.top = readLong(opts.get("$top"), defaultTop, maxTop); + r.skip = Math.max(0, readLong(opts.get("$skip"), 0, Long.MAX_VALUE)); + r.wantsCount = "true".equalsIgnoreCase(trim(opts.get("$count"))); + r.key = keyValue(uri, opts, keyField); + + try { + String filter = trim(opts.get("$filter")); + if (!filter.isEmpty()) { + r.filterSql = " WHERE " + new FilterParser(filter, cols, d).parseAll(); + } + } catch (IllegalArgumentException e) { + // Rejected rather than dropped: answering a filter you could not read + // returns more rows than the client asked for, and looks like success. + r.rejected = true; + r.rejectReason = e.getMessage(); + } + + List terms = sortTerms(trim(opts.get("$orderby")), cols); + if (!terms.isEmpty()) { + r.sortColumn1 = terms.get(0)[0]; + r.sortDirection1 = terms.get(0)[1]; + } + if (terms.size() > 1) { + r.sortColumn2 = terms.get(1)[0]; + r.sortDirection2 = terms.get(1)[1]; + } + r.orderBySql = orderBySql(terms, cols, defaultOrderBy, d, r.top, r.skip); + return r; + } + + /** + * The key alone, for a resource whose SQL already answers everything else. + * + *

Plenty of resources are reached only one way — "the sessions of this + * weekend", "the laps of this race" — and their whole contract is a single + * id out of {@code $filter}. Making those build a column map and a dialect + * to get at one string would be a tax on the common case, so this is the + * short form of the same parse. + */ + public static String key(String uri, String keyField) { + return keyValue(uri, parseQuery(uri), keyField); + } + + /** + * The number a {@code $filter} term compares a field to, or the fallback. + * + *

Distinct from {@link #key} in one way that matters: it never falls back + * to the path segment. A resource keyed on {@code calendarKey} can also be + * asked {@code ?$filter=year eq 2021}, and reading 1036 out of + * {@code /Calendar('1036-c')} as if it were a year would answer a question + * nobody asked. + * + *

The quotes are optional because the client's are: the same numeric + * column arrives as {@code year eq 1957} from a grid header and + * {@code year eq '1957'} from a combo box. + */ + public static long filterNumber(String uri, String field, long fallback) { + String filter = trim(parseQuery(uri).get("$filter")); + String f = trim(field); + if (filter.isEmpty() || f.isEmpty()) { + return fallback; + } + Matcher m = Pattern.compile( + "(?:^|\\s|\\()" + Pattern.quote(f) + "\\s+eq\\s+'?(-?[0-9]{1,18})'?(?:\\s|\\)|$)", + Pattern.CASE_INSENSITIVE).matcher(filter); + if (!m.find()) { + return fallback; + } + try { + return Long.parseLong(m.group(1)); + } catch (NumberFormatException e) { + return fallback; + } + } + + // ------------------------------------------------------------- dialects + + /** The handful of places SQL engines disagree about what this code emits. */ + private enum Dialect { + POSTGRESQL, DUCKDB, SQLSERVER, ORACLE, MYSQL; + + static Dialect of(String s) { + if (s == null) { + return POSTGRESQL; + } + switch (s.trim().toLowerCase()) { + case "duckdb": return DUCKDB; + case "sqlserver": + case "mssql": return SQLSERVER; + case "oracle": return ORACLE; + case "mysql": return MYSQL; + default: return POSTGRESQL; + } + } + + /** Case-insensitive LIKE, which only two of these spell the same way. */ + String ilike(String col, String pattern) { + switch (this) { + case POSTGRESQL: + case DUCKDB: + return col + " ILIKE " + pattern; + case SQLSERVER: + // Default collations are already case-insensitive; LOWER on + // both sides is the portable form and costs an index scan + // either way once a leading wildcard is involved. + return "LOWER(" + col + ") LIKE LOWER(" + pattern + ")"; + default: + return "LOWER(" + col + ") LIKE LOWER(" + pattern + ")"; + } + } + + /** The page window. Three spellings across five engines. */ + String page(long top, long skip) { + switch (this) { + case SQLSERVER: + case ORACLE: + // Both require an ORDER BY before OFFSET, which is why the + // caller always supplies a default order. + return " OFFSET " + skip + " ROWS FETCH NEXT " + top + " ROWS ONLY"; + case MYSQL: + return " LIMIT " + skip + ", " + top; + default: + return " LIMIT " + top + (skip > 0 ? " OFFSET " + skip : ""); + } + } + } + + // --------------------------------------------------------------- pieces + + /** Query options from a URI, keys lower-cased and values URL-decoded. */ + public static Map parseQuery(String uri) { + Map out = new HashMap<>(); + if (uri == null) { + return out; + } + int q = uri.indexOf('?'); + if (q < 0 || q == uri.length() - 1) { + return out; + } + for (String pair : uri.substring(q + 1).split("&")) { + int eq = pair.indexOf('='); + if (eq > 0) { + out.put(decode(pair.substring(0, eq)).toLowerCase(), decode(pair.substring(eq + 1))); + } + } + return out; + } + + private static String decode(String s) { + try { + return URLDecoder.decode(s, StandardCharsets.UTF_8.name()); + } catch (Exception e) { + return s; + } + } + + private static Map parseColumnMap(String columnMap) { + Map cols = new LinkedHashMap<>(); + if (columnMap == null) { + return cols; + } + for (String entry : columnMap.split(",")) { + String[] bits = entry.trim().split(":"); + if (bits.length >= 2 && !bits[0].trim().isEmpty()) { + String type = bits.length >= 3 ? columnType(bits[2]) : Column.TEXT; + cols.put(bits[0].trim().toLowerCase(), new Column(bits[1].trim(), type)); + } + } + return cols; + } + + /** + * A column type, or a hard failure. + * + *

An unrecognised type could default to text, which is what the previous + * helper did by having no types at all. But a typo would then quietly turn + * {@code year:t.year:numbr} into a text column, and {@code year eq 1957} + * into {@code t.year = '1957'} — zero rows, status 200, no log line. The + * whole point of this component is that a request it cannot honour says so, + * and a column map it cannot read is the same class of mistake. This is + * design-time input, so failing on the first request is the cheapest place + * to find out. + */ + private static String columnType(String raw) { + switch (raw.trim().toLowerCase()) { + case "text": + case "string": + return Column.TEXT; + case "number": + case "int": + case "integer": + case "long": + case "decimal": + return Column.NUMBER; + case "bool": + case "boolean": + return Column.BOOL; + case "date": + case "datetime": + return Column.DATE; + default: + throw new IllegalArgumentException( + "unknown column type '" + raw.trim() + "'; expected text, number, bool or date"); + } + } + + private static String trim(String s) { + return s == null ? "" : s.trim(); + } + + private static long readLong(String raw, long fallback, long max) { + if (raw == null) { + return fallback; + } + try { + long v = Long.parseLong(raw.trim()); + if (v < 0) { + return fallback; + } + return v > max ? max : v; + } catch (NumberFormatException e) { + return fallback; + } + } + + // ------------------------------------------------------------------ key + + private static final Pattern PATH_KEY = Pattern.compile( + "\\(\\s*(?:[A-Za-z_][A-Za-z0-9_]*\\s*=\\s*)?'?([^')]*)'?\\s*\\)\\s*/?$"); + + /** + * The key a client is re-reading a held row by, in any of the three + * spellings it arrives in. + * + *

This is the one that bites. A client holding a row re-reads it by key + * on its own initiative; a read that ignores that request answers with the + * collection default, and the client then adopts the first row of the answer + * as that object's identity — permanently, with a valid payload and a 200 at + * every step. FINDINGS §37. + */ + private static String keyValue(String uri, Map opts, String keyField) { + String field = trim(keyField); + if (!field.isEmpty()) { + String filter = trim(opts.get("$filter")); + Matcher m = Pattern.compile( + "(?:^|\\s|\\()" + Pattern.quote(field) + "\\s+eq\\s+('([^']*)'|[0-9][0-9.]*)", + Pattern.CASE_INSENSITIVE).matcher(filter); + if (m.find()) { + String v = m.group(2) != null ? m.group(2) : m.group(1); + if (safeKey(v)) { + return v; + } + } + } + int q = uri == null ? -1 : uri.indexOf('?'); + String path = uri == null ? "" : (q < 0 ? uri : uri.substring(0, q)); + Matcher pm = PATH_KEY.matcher(path); + if (pm.find() && safeKey(pm.group(1))) { + return pm.group(1); + } + return ""; + } + + private static boolean safeKey(String v) { + return v != null && v.matches("[A-Za-z0-9_.\\-]{1,128}"); + } + + // ---------------------------------------------------------------- order + + private static List sortTerms(String orderby, Map cols) { + List out = new ArrayList<>(); + if (orderby.isEmpty()) { + return out; + } + for (String term : orderby.split(",")) { + String[] bits = term.trim().split("\\s+"); + if (bits.length == 0 || bits[0].isEmpty()) { + continue; + } + if (!cols.containsKey(bits[0].toLowerCase())) { + continue; // not whitelisted: a wrong order is cosmetic, so ignore it + } + out.add(new String[]{bits[0], bits.length > 1 && "desc".equalsIgnoreCase(bits[1]) ? "D" : "A"}); + } + return out; + } + + private static String orderBySql(List terms, Map cols, + String defaultOrderBy, Dialect d, long top, long skip) { + List parts = new ArrayList<>(); + for (String[] t : terms) { + parts.add(cols.get(t[0].toLowerCase()).sql + ("D".equals(t[1]) ? " DESC" : " ASC")); + } + if (parts.isEmpty() && !trim(defaultOrderBy).isEmpty()) { + parts.add(defaultOrderBy.trim()); + } + StringBuilder sb = new StringBuilder(); + if (!parts.isEmpty()) { + sb.append(" ORDER BY ").append(String.join(", ", parts)); + } + sb.append(d.page(top, skip)); + return sb.toString(); + } + + // --------------------------------------------------------------- filter + + /** + * Recursive descent over the filter grammar, so precedence and parentheses + * are handled rather than approximated. + * + *

An earlier version split the string on {@code and} and matched each + * piece with a regex. That cannot express {@code or} at all — which Mendix + * emits the moment a datagrid has two values selected in one filter — and it + * cannot see that {@code a and (b or c)} groups. + */ + private static final class FilterParser { + private final String src; + private final Map cols; + private final Dialect d; + private int pos; + + FilterParser(String src, Map cols, Dialect d) { + this.src = src; + this.cols = cols; + this.d = d; + } + + String parseAll() { + String sql = parseOr(); + skipSpace(); + if (pos < src.length()) { + throw new IllegalArgumentException("unparsed input at " + pos + ": " + src.substring(pos)); + } + return sql; + } + + private String parseOr() { + String left = parseAnd(); + while (keyword("or")) { + left = "(" + left + " OR " + parseAnd() + ")"; + } + return left; + } + + private String parseAnd() { + String left = parseUnary(); + while (keyword("and")) { + left = "(" + left + " AND " + parseUnary() + ")"; + } + return left; + } + + private String parseUnary() { + if (keyword("not")) { + return "(NOT " + parseUnary() + ")"; + } + return parsePrimary(); + } + + private String parsePrimary() { + skipSpace(); + if (peek() == '(') { + int save = pos; + pos++; + String inner = parseOr(); + skipSpace(); + if (peek() != ')') { + pos = save; + throw new IllegalArgumentException("unbalanced parenthesis"); + } + pos++; + return "(" + inner + ")"; + } + return parseTerm(); + } + + private static final Pattern FN = Pattern.compile( + "\\G(contains|startswith|endswith)\\s*\\(\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*,\\s*('(?:[^']|'')*')\\s*\\)", + Pattern.CASE_INSENSITIVE); + private static final Pattern CMP = Pattern.compile( + "\\G([A-Za-z_][A-Za-z0-9_]*)\\s+(eq|ne|gt|ge|lt|le)\\s+" + + "('(?:[^']|'')*'|-?[0-9][0-9.]*|true|false|null|" + + "[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9:.]+Z?)", + Pattern.CASE_INSENSITIVE); + + private String parseTerm() { + skipSpace(); + Matcher fn = FN.matcher(src); + fn.region(pos, src.length()); + if (fn.find()) { + pos = fn.end(); + Column c = require(fn.group(2)); + String v = esc(unquote(fn.group(3))); + String kind = fn.group(1).toLowerCase(); + String pattern = "contains".equals(kind) ? "'%" + v + "%'" + : "startswith".equals(kind) ? "'" + v + "%'" : "'%" + v + "'"; + // Substring search against a number or a date is meaningful — a + // user typing "195" into a year filter means the 1950s — but only + // once the column is text. LIKE against a BIGINT is a type error + // on some engines and a silent cast on others. + String col = Column.TEXT.equals(c.type) ? c.sql : "CAST(" + c.sql + " AS VARCHAR)"; + return d.ilike(col, pattern); + } + Matcher cmp = CMP.matcher(src); + cmp.region(pos, src.length()); + if (cmp.find()) { + pos = cmp.end(); + Column c = require(cmp.group(1)); + String op = cmp.group(2).toLowerCase(); + String val = cmp.group(3); + return comparison(c, op, val); + } + throw new IllegalArgumentException("cannot translate: " + src.substring(pos)); + } + + /** + * One comparison, rendered for the column's own type rather than for the + * shape the client happened to send. + */ + private String comparison(Column c, String op, String val) { + if ("null".equalsIgnoreCase(val)) { + if ("eq".equals(op)) { + return c.sql + " IS NULL"; + } + if ("ne".equals(op)) { + return c.sql + " IS NOT NULL"; + } + throw new IllegalArgumentException("null only compares with eq or ne"); + } + String sqlOp = sqlOp(op); + String bare = val.startsWith("'") ? unquote(val) : val; + + if (Column.NUMBER.equals(c.type)) { + if (!bare.matches("-?[0-9]+(\\.[0-9]+)?")) { + throw new IllegalArgumentException("not a number for " + c.sql + ": " + bare); + } + return c.sql + " " + sqlOp + " " + bare; + } + if (Column.BOOL.equals(c.type)) { + if (!"true".equalsIgnoreCase(bare) && !"false".equalsIgnoreCase(bare)) { + throw new IllegalArgumentException("not a boolean for " + c.sql + ": " + bare); + } + return c.sql + " " + sqlOp + " " + bare.toLowerCase(); + } + if (Column.DATE.equals(c.type)) { + // Quoted and cast rather than pasted, so the engine parses the + // instant instead of this code guessing a format. + return c.sql + " " + sqlOp + " CAST('" + esc(bare) + "' AS TIMESTAMP)"; + } + return c.sql + " " + sqlOp + " '" + esc(bare) + "'"; + } + + private Column require(String name) { + Column c = cols.get(name.toLowerCase()); + if (c == null) { + throw new IllegalArgumentException("field not filterable: " + name); + } + return c; + } + + private boolean keyword(String kw) { + skipSpace(); + int end = pos + kw.length(); + if (end <= src.length() && src.regionMatches(true, pos, kw, 0, kw.length()) + && (end == src.length() || !Character.isLetterOrDigit(src.charAt(end)))) { + pos = end; + return true; + } + return false; + } + + private void skipSpace() { + while (pos < src.length() && Character.isWhitespace(src.charAt(pos))) { + pos++; + } + } + + private char peek() { + return pos < src.length() ? src.charAt(pos) : '\0'; + } + } + + private static String sqlOp(String odataOp) { + switch (odataOp) { + case "eq": return "="; + case "ne": return "<>"; + case "gt": return ">"; + case "ge": return ">="; + case "lt": return "<"; + case "le": return "<="; + default: throw new IllegalArgumentException("unsupported operator: " + odataOp); + } + } + + private static String unquote(String literal) { + String s = literal.substring(1, literal.length() - 1); + return s.replace("''", "'"); + } + + private static String esc(String v) { + return v.replace("'", "''"); + } +} diff --git a/.claude/skills/packs/mendix-odata-pushdown/java/QueryObject.java b/.claude/skills/packs/mendix-odata-pushdown/java/QueryObject.java new file mode 100644 index 000000000..b46dd91aa --- /dev/null +++ b/.claude/skills/packs/mendix-odata-pushdown/java/QueryObject.java @@ -0,0 +1,64 @@ +package {{MODULE_PATH}}; + +import com.mendix.core.Core; +import com.mendix.systemwideinterfaces.core.IContext; +import com.mendix.systemwideinterfaces.core.IMendixObject; + +/** + * The Mendix binding for {@link ODataQueryParser}. + * + *

Kept apart from the parser on purpose. The parser is plain Java over + * strings — it can be exercised from a JUnit test, a {@code main}, or a jshell + * session with no runtime around it, which is how the grammar in FINDINGS §45 + * was checked term by term. This class is the only part that needs a Mendix + * context, and all it does is copy fields onto an object. + */ +public final class QueryObject { + + /** The entity this component publishes its answer as. */ + public static final String ENTITY = "ODataPushdown.Query"; + + private QueryObject() { + } + + /** + * Runs the parse and returns the result as an {@code ODataPushdown.Query}. + * + *

Non-persistent, so nothing is committed and nothing needs cleaning up; + * the object lives as long as the microflow that asked for it. + */ + public static IMendixObject parse(IContext context, String uri, String columns, + String dialect, Long maxTop, Long defaultTop, + String defaultOrderBy, String keyField, + Boolean rejectUnsupported) { + ODataQueryParser.Result r = ODataQueryParser.parse( + uri, columns, dialect, + maxTop == null ? 500L : maxTop, + defaultTop == null ? 100L : defaultTop, + defaultOrderBy, keyField); + + if (r.rejected && Boolean.TRUE.equals(rejectUnsupported)) { + // For a caller that splices, the alternative to throwing is an empty + // WHERE — every row in the table, under a 200, in answer to a + // request for a few of them. A 500 is the honest response to a + // question this cannot read. + throw new IllegalArgumentException( + "cannot translate OData query: " + r.rejectReason); + } + + IMendixObject o = Core.instantiate(context, ENTITY); + o.setValue(context, "FilterSql", r.filterSql); + o.setValue(context, "OrderBySql", r.orderBySql); + o.setValue(context, "Key", r.key); + o.setValue(context, "Top", r.top); + o.setValue(context, "Skip", r.skip); + o.setValue(context, "WantsCount", r.wantsCount); + o.setValue(context, "SortColumn1", r.sortColumn1); + o.setValue(context, "SortDirection1", r.sortDirection1); + o.setValue(context, "SortColumn2", r.sortColumn2); + o.setValue(context, "SortDirection2", r.sortDirection2); + o.setValue(context, "Rejected", r.rejected); + o.setValue(context, "RejectReason", r.rejectReason); + return o; + } +} diff --git a/.claude/skills/packs/mendix-odata-pushdown/java/RoutineCall.java b/.claude/skills/packs/mendix-odata-pushdown/java/RoutineCall.java new file mode 100644 index 000000000..0d74cca2e --- /dev/null +++ b/.claude/skills/packs/mendix-odata-pushdown/java/RoutineCall.java @@ -0,0 +1,185 @@ +package {{MODULE_PATH}}; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +/** + * How to invoke a stored routine, in the spelling the engine wants. + * + *

What this is for. The rest of this module puts a query behind an + * OData resource. This puts a routine behind one — a stored procedure, + * a table-valued function, a package body — which is where a great deal of the + * logic worth exposing actually lives, and the part of a legacy system nobody + * gets to rewrite. + * + *

What it deliberately does not do. It never renders a value. Every + * argument comes out as a Mendix named parameter — {@code {driverId}} — so the + * statement it returns is a template that {@code execute database query} binds. + * There is no escaping here because there is nothing to escape: the only text + * this class emits is the routine name, which is checked against an identifier + * pattern, and punctuation. + * + *

That is a stronger position than the {@code $filter} translation can take. + * A {@code WHERE} clause has to be built as text because its shape + * comes from the client; a routine call's shape is fixed by the routine, and + * only the values vary. So they can be bound, and they are. + * + *

Why it is not just string concatenation. Five engines, three kinds + * of routine, and almost no two agree: + * + *

+ *   table-valued function        procedure
+ *   ─────────────────────────    ────────────────────────────
+ *   pg   SELECT * FROM f(a,b)    CALL p(a,b,NULL,NULL)
+ *   mssql SELECT * FROM f(a,b)   EXEC p @x = a, @y = b
+ *   ora  SELECT * FROM TABLE(f(a,b))
+ *                                BEGIN p(a,b); END;
+ *   mysql (none — use a proc)    CALL p(a,b)
+ *   duck SELECT * FROM f(a,b)    (none — macros only)
+ * 
+ * + * Getting that wrong is a whole afternoon, once per engine. + */ +public final class RoutineCall { + + /** A schema-qualified routine name and nothing else. */ + private static final Pattern ROUTINE = + Pattern.compile("[A-Za-z_][A-Za-z0-9_]{0,62}(\\.[A-Za-z_][A-Za-z0-9_]{0,62}){0,2}"); + + /** A Mendix query parameter name. */ + private static final Pattern PARAM = Pattern.compile("[A-Za-z_][A-Za-z0-9_]{0,62}"); + + private RoutineCall() { + } + + /** + * The statement to run, with one bound parameter per argument. + * + * @param routine schema-qualified name, e.g. {@code f1ops.driver_form} + * @param kind {@code table} | {@code procedure} | {@code scalar} + * @param parameters comma-separated Mendix parameter names, in the routine's + * own argument order; {@code null} for a literal SQL NULL, + * which is how a Postgres procedure's INOUT slots are + * filled + * @param dialect postgresql | duckdb | sqlserver | oracle | mysql + */ + public static String statement(String routine, String kind, String parameters, String dialect) { + String name = trim(routine); + if (!ROUTINE.matcher(name).matches()) { + throw new IllegalArgumentException("not a routine name: " + routine); + } + String d = trim(dialect).toLowerCase(); + String k = trim(kind).toLowerCase(); + List args = args(parameters, d); + + switch (k) { + case "table": + return tableCall(name, args, d); + case "procedure": + return procedureCall(name, args, parameters, d); + case "scalar": + return "SELECT " + name + "(" + join(args) + ") AS result"; + default: + throw new IllegalArgumentException( + "unknown routine kind '" + kind + "'; expected table, procedure or scalar"); + } + } + + // ------------------------------------------------------------------ kinds + + private static String tableCall(String name, List args, String d) { + if ("oracle".equals(d)) { + // A pipelined function is a table only inside TABLE(). + return "SELECT * FROM TABLE(" + name + "(" + join(args) + "))"; + } + if ("mysql".equals(d)) { + // MySQL has no table-valued functions at all. Saying so beats + // emitting something that parses and returns one scalar column. + throw new IllegalArgumentException( + "mysql has no table-valued functions; use kind 'procedure'"); + } + return "SELECT * FROM " + name + "(" + join(args) + ")"; + } + + private static String procedureCall(String name, List args, String rawParams, String d) { + switch (d) { + case "sqlserver": + // Named arguments, because EXEC positional and named cannot mix + // and named is the form that survives a routine gaining a + // parameter with a default. + return "EXEC " + name + named(rawParams); + case "oracle": + return "BEGIN " + name + "(" + join(args) + "); END;"; + case "duckdb": + throw new IllegalArgumentException( + "duckdb has no stored procedures; use kind 'table' over a macro"); + default: + // Postgres and MySQL. In Postgres a CALL with INOUT parameters + // answers with one row carrying them, which is what makes a + // procedure readable through the same query interface as a + // SELECT. + return "CALL " + name + "(" + join(args) + ")"; + } + } + + // ------------------------------------------------------------------ args + + /** + * One placeholder per named parameter. + * + *

{@code null} is passed through as a literal, because a Postgres + * procedure's INOUT slots have to be present in the call and there is + * nothing to bind into them — the engine fills them on the way out. + */ + private static List args(String parameters, String dialect) { + List out = new ArrayList<>(); + if (trim(parameters).isEmpty()) { + return out; + } + for (String raw : parameters.split(",")) { + String p = raw.trim(); + if (p.isEmpty()) { + continue; + } + if ("null".equalsIgnoreCase(p)) { + out.add("NULL"); + continue; + } + if (!PARAM.matcher(p).matches()) { + throw new IllegalArgumentException("not a parameter name: " + p); + } + out.add("{" + p + "}"); + } + return out; + } + + /** SQL Server's {@code @name = {name}} form. */ + private static String named(String parameters) { + StringBuilder sb = new StringBuilder(); + if (trim(parameters).isEmpty()) { + return ""; + } + boolean first = true; + for (String raw : parameters.split(",")) { + String p = raw.trim(); + if (p.isEmpty() || "null".equalsIgnoreCase(p)) { + continue; // SQL Server OUTPUT slots are declared, not passed + } + if (!PARAM.matcher(p).matches()) { + throw new IllegalArgumentException("not a parameter name: " + p); + } + sb.append(first ? " " : ", ").append('@').append(p).append(" = {").append(p).append('}'); + first = false; + } + return sb.toString(); + } + + private static String join(List args) { + return String.join(", ", args); + } + + private static String trim(String s) { + return s == null ? "" : s.trim(); + } +} diff --git a/.claude/skills/packs/mendix-odata-pushdown/java/actions/CallStatement.java b/.claude/skills/packs/mendix-odata-pushdown/java/actions/CallStatement.java new file mode 100644 index 000000000..25ff4354c --- /dev/null +++ b/.claude/skills/packs/mendix-odata-pushdown/java/actions/CallStatement.java @@ -0,0 +1,59 @@ +// This file was generated by Mendix Studio Pro. +// +// WARNING: Only the following code will be retained when actions are regenerated: +// - the import list +// - the code between BEGIN USER CODE and END USER CODE +// - the code between BEGIN EXTRA CODE and END EXTRA CODE +// Other code you write will be lost the next time you deploy the project. +// Special characters, e.g., é, ö, à, etc. are supported in comments. + +package {{MODULE_PATH}}.actions; + +import com.mendix.systemwideinterfaces.core.IContext; +import com.mendix.systemwideinterfaces.core.UserAction; + +public class CallStatement extends UserAction +{ + private final java.lang.String Routine; + private final java.lang.String Kind; + private final java.lang.String Parameters; + private final java.lang.String Dialect; + + public CallStatement( + IContext context, + java.lang.String _routine, + java.lang.String _kind, + java.lang.String _parameters, + java.lang.String _dialect + ) + { + super(context); + this.Routine = _routine; + this.Kind = _kind; + this.Parameters = _parameters; + this.Dialect = _dialect; + } + + @java.lang.Override + public java.lang.String executeAction() throws Exception + { + // BEGIN USER CODE + + return {{MODULE_PATH}}.RoutineCall.statement(Routine, Kind, Parameters, Dialect); + + // END USER CODE + } + + /** + * Returns a string representation of this action + * @return a string representation of this action + */ + @java.lang.Override + public java.lang.String toString() + { + return "CallStatement"; + } + + // BEGIN EXTRA CODE + // END EXTRA CODE +} diff --git a/.claude/skills/packs/mendix-odata-pushdown/java/actions/FilterNumber.java b/.claude/skills/packs/mendix-odata-pushdown/java/actions/FilterNumber.java new file mode 100644 index 000000000..e8407f558 --- /dev/null +++ b/.claude/skills/packs/mendix-odata-pushdown/java/actions/FilterNumber.java @@ -0,0 +1,56 @@ +// This file was generated by Mendix Studio Pro. +// +// WARNING: Only the following code will be retained when actions are regenerated: +// - the import list +// - the code between BEGIN USER CODE and END USER CODE +// - the code between BEGIN EXTRA CODE and END EXTRA CODE +// Other code you write will be lost the next time you deploy the project. +// Special characters, e.g., é, ö, à, etc. are supported in comments. + +package {{MODULE_PATH}}.actions; + +import com.mendix.systemwideinterfaces.core.IContext; +import com.mendix.systemwideinterfaces.core.UserAction; + +public class FilterNumber extends UserAction +{ + private final java.lang.String Uri; + private final java.lang.String Field; + private final java.lang.Long Fallback; + + public FilterNumber( + IContext context, + java.lang.String _uri, + java.lang.String _field, + java.lang.Long _fallback + ) + { + super(context); + this.Uri = _uri; + this.Field = _field; + this.Fallback = _fallback; + } + + @java.lang.Override + public java.lang.Long executeAction() throws Exception + { + // BEGIN USER CODE + + return {{MODULE_PATH}}.ODataQueryParser.filterNumber(Uri, Field, Fallback); + + // END USER CODE + } + + /** + * Returns a string representation of this action + * @return a string representation of this action + */ + @java.lang.Override + public java.lang.String toString() + { + return "FilterNumber"; + } + + // BEGIN EXTRA CODE + // END EXTRA CODE +} diff --git a/.claude/skills/packs/mendix-odata-pushdown/java/actions/Key.java b/.claude/skills/packs/mendix-odata-pushdown/java/actions/Key.java new file mode 100644 index 000000000..b90933120 --- /dev/null +++ b/.claude/skills/packs/mendix-odata-pushdown/java/actions/Key.java @@ -0,0 +1,53 @@ +// This file was generated by Mendix Studio Pro. +// +// WARNING: Only the following code will be retained when actions are regenerated: +// - the import list +// - the code between BEGIN USER CODE and END USER CODE +// - the code between BEGIN EXTRA CODE and END EXTRA CODE +// Other code you write will be lost the next time you deploy the project. +// Special characters, e.g., é, ö, à, etc. are supported in comments. + +package {{MODULE_PATH}}.actions; + +import com.mendix.systemwideinterfaces.core.IContext; +import com.mendix.systemwideinterfaces.core.UserAction; + +public class Key extends UserAction +{ + private final java.lang.String Uri; + private final java.lang.String KeyField; + + public Key( + IContext context, + java.lang.String _uri, + java.lang.String _keyField + ) + { + super(context); + this.Uri = _uri; + this.KeyField = _keyField; + } + + @java.lang.Override + public java.lang.String executeAction() throws Exception + { + // BEGIN USER CODE + + return {{MODULE_PATH}}.ODataQueryParser.key(Uri, KeyField); + + // END USER CODE + } + + /** + * Returns a string representation of this action + * @return a string representation of this action + */ + @java.lang.Override + public java.lang.String toString() + { + return "Key"; + } + + // BEGIN EXTRA CODE + // END EXTRA CODE +} diff --git a/.claude/skills/packs/mendix-odata-pushdown/java/actions/Parse.java b/.claude/skills/packs/mendix-odata-pushdown/java/actions/Parse.java new file mode 100644 index 000000000..e1972b16b --- /dev/null +++ b/.claude/skills/packs/mendix-odata-pushdown/java/actions/Parse.java @@ -0,0 +1,73 @@ +// This file was generated by Mendix Studio Pro. +// +// WARNING: Only the following code will be retained when actions are regenerated: +// - the import list +// - the code between BEGIN USER CODE and END USER CODE +// - the code between BEGIN EXTRA CODE and END EXTRA CODE +// Other code you write will be lost the next time you deploy the project. +// Special characters, e.g., é, ö, à, etc. are supported in comments. + +package {{MODULE_PATH}}.actions; + +import com.mendix.systemwideinterfaces.core.IContext; +import com.mendix.systemwideinterfaces.core.UserAction; +import com.mendix.systemwideinterfaces.core.IMendixObject; + +public class Parse extends UserAction +{ + private final java.lang.String Uri; + private final java.lang.String Columns; + private final java.lang.String Dialect; + private final java.lang.Long MaxTop; + private final java.lang.Long DefaultTop; + private final java.lang.String DefaultOrderBy; + private final java.lang.String KeyField; + private final java.lang.Boolean RejectUnsupported; + + public Parse( + IContext context, + java.lang.String _uri, + java.lang.String _columns, + java.lang.String _dialect, + java.lang.Long _maxTop, + java.lang.Long _defaultTop, + java.lang.String _defaultOrderBy, + java.lang.String _keyField, + java.lang.Boolean _rejectUnsupported + ) + { + super(context); + this.Uri = _uri; + this.Columns = _columns; + this.Dialect = _dialect; + this.MaxTop = _maxTop; + this.DefaultTop = _defaultTop; + this.DefaultOrderBy = _defaultOrderBy; + this.KeyField = _keyField; + this.RejectUnsupported = _rejectUnsupported; + } + + @java.lang.Override + public IMendixObject executeAction() throws Exception + { + // BEGIN USER CODE + + return {{MODULE_PATH}}.QueryObject.parse(getContext(), Uri, Columns, Dialect, + MaxTop, DefaultTop, DefaultOrderBy, KeyField, RejectUnsupported); + + // END USER CODE + } + + /** + * Returns a string representation of this action + * @return a string representation of this action + */ + @java.lang.Override + public java.lang.String toString() + { + return "Parse"; + } + + // BEGIN EXTRA CODE + // END EXTRA CODE +} diff --git a/.claude/skills/packs/mendix-odata-pushdown/mdl/module.mdl b/.claude/skills/packs/mendix-odata-pushdown/mdl/module.mdl new file mode 100644 index 000000000..5602d0aaf --- /dev/null +++ b/.claude/skills/packs/mendix-odata-pushdown/mdl/module.mdl @@ -0,0 +1,274 @@ +/* + * {{MODULE}} — serve an OData resource from SQL you did not write. + * + * Mendix can publish any entity over OData, including one that has no table + * behind it: you declare the resource non-persistable and give it a read + * microflow. What the documentation does not say, and what costs a day to find + * out, is that Mendix then applies NONE of the query options to your answer. + * `$filter`, `$orderby`, `$top`, `$skip`, `$count` and the key lookup all arrive + * on the request URI and all stay there. Whatever the microflow returns is + * exactly what the client gets. + * + * That failure mode is the reason this module exists. It is not a 500 and it is + * not an empty grid — it is a 200 with the wrong rows. `?$top=5` returns 917 + * rows and the widget shows five of them, so paging looks fine while every page + * ships the whole table. A client re-reading one held row by its key gets the + * collection back, adopts the first row as that object's identity, and keeps it: + * the season page for 1957 lists the 2024 grid, and nothing anywhere logs a + * word. (FINDINGS §37 is the day that one took.) + * + * So the read microflow has to honour the request itself, and this module is + * the part of that which is the same for everybody: + * + * {{MODULE}}.Parse(Uri, Columns, Dialect, MaxTop, DefaultTop, + * DefaultOrderBy, KeyField) -> {{MODULE}}.Query + * {{MODULE}}.Key(Uri, KeyField) -> String + * + * Nothing in it is Formula 1, or DuckDB, or this app. Point it at a warehouse + * table, a legacy view, a Databricks catalogue or a stored procedure's result + * set and it does the same job: a published OData surface over data Mendix + * cannot see, with widgets on the far side that filter, sort and page for real. + * + * + * TWO WAYS TO SPEND THE ANSWER + * + * Which one you get is decided by whether you own the SQL. + * + * Splice — you build the statement, so take `FilterSql` (" WHERE …") and + * `OrderBySql` (" ORDER BY … LIMIT … OFFSET …") and concatenate them. One + * parse, one statement, everything pushed down. + * + * Bind — the SQL lives somewhere you cannot rewrite: a named query on a + * database connection, a view, a procedure. Take `Key`, `Top`, `Skip`, + * `SortColumn1` and `SortDirection1` and pass them as parameters. The SQL has + * to be written to expect them, but they travel as values, so no string of + * yours ever reaches the parser on the far side. + * + * The second style is why this is not simply a SQL builder. Most of the data + * worth exposing this way is behind SQL somebody else owns. + * + * + * WHAT IT UNDERSTANDS + * + * The whole grammar Mendix's own OData client emits, captured off the wire from + * a running app rather than read off the specification — see FINDINGS §45 for + * the capture. Comparisons eq/ne/gt/ge/lt/le, contains/startswith/endswith, + * `and`, `or`, `not`, parentheses, `eq null`, booleans, decimals and ISO + * instants; `$orderby` with two terms; `$top`, `$skip`, `$count`, and the key in + * all three spellings it arrives in. That set is the bar for a widget over an + * external entity to work rather than half-work. + * + * OData itself is far larger — arithmetic, lambdas, `$apply`, date functions, + * `any`/`all`. None of it is emitted by a Mendix client, so none of it is here. + * A request outside the grammar is REJECTED, not ignored: `Rejected` comes back + * true and the caller is expected to fail the request. Dropping a filter you + * could not read returns more rows than were asked for and calls it success, + * which is the same bug this module exists to stop. + * + * + * INSTALLING IT ELSEWHERE + * + * mxcli skill add mendix-odata-pushdown --apply -p App.mpr + * + * That copies java/ into javasource/{{MODULE_PATH}}/ and runs this script. + * Then add {{MODULE}}.User to whichever user roles run your read microflows — + * the module cannot do that itself without knowing your role names. + * + * No jar and no dependency. See SKILL.md and references/. + */ + +create module {{MODULE}}; + +/* + * The parse result. + * + * Non-persistable: it is a return value, not a record. Nothing commits it and + * nothing cleans it up — it lives as long as the microflow that asked for it. + * + * It is one object because the alternative was ten Java actions, and the app + * this came out of had them. Each re-read the URI, re-split the query string and + * re-parsed the filter to answer one question about it — seven parses of the + * same string in a single read microflow, with nothing holding the seven answers + * to one interpretation. One parse now. + */ +@Position(100, 100) +create or modify non-persistent entity {{MODULE}}.Query ( + /** " WHERE …", or empty when the client asked for the whole collection. */ + FilterSql: String(4000), + /** " ORDER BY … LIMIT n OFFSET m", for callers that build their own SQL. */ + OrderBySql: String(2000), + /** The key the client is re-reading one row by; empty for a collection. */ + Key: String(200), + /** Page size, already clamped to the resource's MaxTop. */ + Top: Long, + Skip: Long, + /** $count=true: the client wants the size of the set, not of the page. */ + WantsCount: Boolean default false, + /** The sort, as exposed names, for callers that bind rather than splice. */ + SortColumn1: String(100), + /** 'A' or 'D'. One letter because it is a bind parameter, not a clause. */ + SortDirection1: String(1) default 'A', + SortColumn2: String(100), + SortDirection2: String(1) default 'A', + /** + * The request asked for something this cannot translate. + * + * Answer it with an error. Returning rows anyway means returning more rows + * than were asked for, under a 200, which no client can detect. + */ + Rejected: Boolean default false, + RejectReason: String(500) +); + +/* + * The full parse. + * + * Columns is the whitelist and the only thing standing between the client and + * your SQL: `exposedName:sqlExpression:type`, comma-separated. Nothing outside + * it can be filtered or sorted on, and a filter naming something outside it is + * a rejection rather than an omission. + * + * 'name:d.name:text,wins:d.race_wins:number,active:d.is_active:bool' + * + * The type is not decoration. Mendix quotes a literal according to what the + * widget believes the attribute is, which is not always what the column is — a + * combo box on a numeric key sends `year eq '1957'` while the grid header above + * it sends `year eq 1957`. Passing the quotes through gives the engine + * `year = '1957'` against a BIGINT: zero rows, status 200. The type is what + * makes both spellings mean the same thing. An unrecognised type is an error, + * because the alternative is a typo that silently returns nothing. + * + * Dialect is one of postgresql | duckdb | sqlserver | oracle | mysql, and + * decides two things only: how to spell a case-insensitive LIKE, and how to + * spell a page. Everything else this emits is ordinary SQL. + * + * DefaultOrderBy is spliced, not escaped — it is yours, not the client's. Give + * one: SQL Server and Oracle refuse OFFSET without an ORDER BY, and a page + * without a total order is a different set each time it is asked for. + * + * RejectUnsupported decides what happens to a filter this cannot translate, + * and the right answer depends on which style you are in. + * + * Splice callers should pass true. Their WHERE comes from FilterSql, so an + * untranslated filter means an empty WHERE, which means every row in the + * table under a 200. Throwing turns that into a 500 the caller can see. + * + * Bind callers can pass false. They never look at FilterSql — their SQL + * filters on the key and nothing else — so a filter they were never going to + * apply is not made better by failing the request. Read `Rejected` if you + * want to log it. + */ +CREATE OR MODIFY JAVA ACTION {{MODULE}}.Parse( + Uri: String, + Columns: String NOT NULL, + Dialect: String NOT NULL, + MaxTop: Long NOT NULL, + DefaultTop: Long NOT NULL, + DefaultOrderBy: String, + KeyField: String, + RejectUnsupported: Boolean NOT NULL +) RETURNS {{MODULE}}.Query +EXPOSED AS 'Parse OData query options' IN 'OData pushdown' +AS $$ +return {{MODULE_PATH}}.QueryObject.parse(getContext(), Uri, Columns, Dialect, + MaxTop, DefaultTop, DefaultOrderBy, KeyField, RejectUnsupported); +$$; + +/* + * The key on its own. + * + * Plenty of resources are reachable one way only — the sessions of this + * weekend, the laps of this race — and their entire contract is one id out of + * `$filter`. Making those declare a column map and a dialect to reach one + * string would be a tax on the common case, so this is the short form of the + * same parse. It reads the key from `$filter eq …`, quoted or not, and + * from the `(…)` key segment on the path. + */ +CREATE OR MODIFY JAVA ACTION {{MODULE}}.Key( + Uri: String, + KeyField: String NOT NULL +) RETURNS String +EXPOSED AS 'OData key from request' IN 'OData pushdown' +AS $$ +return {{MODULE_PATH}}.ODataQueryParser.key(Uri, KeyField); +$$; + +/* + * A number out of $filter — and not out of the path. + * + * The same resource is often asked two different questions: "the 2021 season" + * (?$filter=year eq 2021) and "this one row" (/Calendar('1036-c')). Key answers + * the second and falls back to the path to do it. This answers the first, and + * must not: reading 1036 out of that path as if it were a year would answer a + * question nobody asked, which is the whole failure mode this module is for. + * + * The quotes are optional because the client's are — the same numeric column + * arrives bare from a grid header and quoted from a combo box. + */ +CREATE OR MODIFY JAVA ACTION {{MODULE}}.FilterNumber( + Uri: String, + Field: String NOT NULL, + Fallback: Long NOT NULL +) RETURNS Long +EXPOSED AS 'OData $filter number' IN 'OData pushdown' +AS $$ +return {{MODULE_PATH}}.ODataQueryParser.filterNumber(Uri, Field, Fallback); +$$; + +/* + * Invoking a stored routine — a procedure, a table-valued function, a package + * body — rather than a query. + * + * This is where a lot of the logic worth exposing actually lives, and the part + * of a legacy system nobody gets to rewrite. Five engines, three kinds of + * routine, and almost no two agree on the spelling: + * + * postgresql SELECT * FROM f(a, b) CALL p(a, b, NULL, NULL) + * sqlserver SELECT * FROM f(a, b) EXEC p @x = a, @y = b + * oracle SELECT * FROM TABLE(f(a, b)) BEGIN p(a, b); END; + * mysql (no table functions) CALL p(a, b) + * duckdb SELECT * FROM macro(a, b) (no procedures) + * + * `Parameters` is a comma-separated list of Mendix query-parameter NAMES, in + * the routine's own argument order — never values. What comes back is a + * statement full of `{placeholders}` for `execute database query` to bind: + * + * CallStatement('f1ops.driver_form', 'table', 'driverId,lastN', 'postgresql') + * -> SELECT * FROM f1ops.driver_form({driverId}, {lastN}) + * + * So unlike the $filter translation there is nothing to escape here: a WHERE + * clause has to be built as text because its shape comes from the client, but + * a routine call's shape is fixed by the routine and only its values vary. The + * only text this emits is the routine name, checked against an identifier + * pattern, and punctuation. + * + * The literal `null` is allowed as a parameter and passes through as SQL NULL — + * that is how a Postgres procedure's INOUT slots are filled. It answers with + * one row carrying them, which is what lets a procedure be read through the + * same interface as a SELECT. + */ +CREATE OR MODIFY JAVA ACTION {{MODULE}}.CallStatement( + Routine: String NOT NULL, + Kind: String NOT NULL, + Parameters: String, + Dialect: String NOT NULL +) RETURNS String +EXPOSED AS 'Stored routine call for this engine' IN 'OData pushdown' +AS $$ +return {{MODULE_PATH}}.RoutineCall.statement(Routine, Kind, Parameters, Dialect); +$$; + +/* + * One role, so the read microflows can instantiate a Query. + * + * A non-persistable entity still needs entity access once project security is + * on. Add {{MODULE}}.User to whichever user roles your published service + * runs as — the module cannot do that itself without knowing your role names. + */ +create or modify module role {{MODULE}}.User; + +grant {{MODULE}}.User on {{MODULE}}.Query ( + create, delete, + read *, + write * +); diff --git a/.claude/skills/packs/mendix-odata-pushdown/pack.yaml b/.claude/skills/packs/mendix-odata-pushdown/pack.yaml new file mode 100644 index 000000000..446ac7602 --- /dev/null +++ b/.claude/skills/packs/mendix-odata-pushdown/pack.yaml @@ -0,0 +1,68 @@ +# Skill pack manifest. See docs/11-proposals/PROPOSAL_skill_packs.md. +# +# This is the third pack the proposal asks for, and the one that needed a +# target the mechanism did not have. `installs.java` now exists; the history of +# why is in references/packaging-gap.md. +name: mendix-odata-pushdown +version: 1.0.0 +description: >- + Push OData query options into the SQL of a resource served by a read + microflow. Mendix applies none of $filter, $orderby, $top, $skip, $count or + the key lookup to a non-persistable resource, and answers 200 with the wrong + rows. Four Java actions that parse the request URI into bound values and SQL + fragments, across five dialects. + +# EXECUTE DATABASE QUERY with a runtime connection override, which is what a +# pushed-down statement runs through. The parser itself is plain Java and works +# further back, but a project that cannot execute the SQL has no use for it. +min_mendix_version: 11.0.0 + +# A Java class's package IS its identity, exactly as a widget id is. Two +# projects whose classes share a package are two projects claiming the same +# class, and the symptom is a compile error in somebody else's module. So the +# source ships with placeholders rather than this project's names, and +# `mxcli skill add` substitutes the destination module's: +# +# {{MODULE}} the Mendix module name e.g. ODataPushdown +# {{MODULE_PATH}} its javasource package e.g. odatapushdown +# +# Every shipped file is declared. The MDL carries both tokens — {{MODULE}} in +# the model names, {{MODULE_PATH}} inside the Java action bodies, which is the +# one place both appear in the same statement. +rewrite: + files: + - mdl/module.mdl + - java/ODataQueryParser.java + - java/QueryObject.java + - java/RoutineCall.java + - java/actions/Parse.java + - java/actions/Key.java + - java/actions/FilterNumber.java + - java/actions/CallStatement.java + +installs: + # Applying this writes a module, an entity, a module role and four Java + # actions, so it is never a side effect of copying documentation: `skill add` + # copies, and only `--apply` executes. + mdl: + - mdl/module.mdl + + # The four action bodies in the MDL are two-line delegations into 882 lines + # of helper classes, and MDL cannot author a standalone class — a Java action + # body is a method body, no class declaration, no imports. Without a way to + # place those helpers the pack would ship an MDL that cannot compile and a + # reader would still copy a directory by hand, which is the manual step the + # pack exists to remove. + # + # Placed into javasource/{{MODULE_PATH}}/, preserving subdirectories, EXCEPT + # java/actions/: mxcli writes those four classes itself from the MDL, so + # placing the pack's copies too would mean two sources of truth for the same + # files and applying the MDL would immediately overwrite them. They stay in + # the pack directory to be read. + # + # An existing file that differs is refused, never overwritten — a locally + # fixed helper and a stale copy look identical from here. + java: + - java + +source: https://github.com/ako/mxcli-formula1/tree/main/model/odatapushdown diff --git a/.claude/skills/packs/mendix-odata-pushdown/references/failure-modes.md b/.claude/skills/packs/mendix-odata-pushdown/references/failure-modes.md new file mode 100644 index 000000000..9673862c9 --- /dev/null +++ b/.claude/skills/packs/mendix-odata-pushdown/references/failure-modes.md @@ -0,0 +1,112 @@ +# What goes wrong + +Symptom first, because that is what you have when you arrive. Every one of these +returns **200**. + +--- + +## A detail page shows a different record's data + +**The client re-read one row by key and got the collection back.** + +Mendix does not apply the key lookup to a resource served by a read microflow. +The client asks for `/Drivers('hamilton')`, receives every driver, adopts the +first row as that object's identity, and **keeps it** — the wrong values persist +in the client's cache until the page is left. + +The fix is `KeyField`. `Parse` fills `Query/Key` from `?$filter=k eq 'v'`, +`/Res('v')` and `/Res(k='v')` alike; `Key` is the short form. A resource with no +`KeyField` cannot answer a single-row read correctly, whatever else it does. + +--- + +## Paging looks right and every page ships the whole table + +`?$top=5` returns all rows; the widget renders five. Nothing is visibly broken +until the table is large enough to notice, and then it looks like a database +problem rather than a contract problem. + +`Query/Top` and `Query/Skip` are clamped to `MaxTop` before you see them. For +splice callers they are already in `OrderBySql`. + +--- + +## A filter returns zero rows against a column that clearly has them + +**The literal arrived quoted and the column is numeric.** + +Mendix quotes according to what the *widget* believes the attribute is. A combo +box on a numeric key sends `year eq '1957'`; the grid header above it sends +`year eq 1957`. Passing the quotes through hands the engine `year = '1957'` +against a BIGINT. + +This is what the third field of the `Columns` map is for. An unrecognised type +is an error rather than a default, because the alternative is a typo that +quietly returns nothing. + +--- + +## `OFFSET` fails on SQL Server or Oracle + +Both refuse `OFFSET` without an `ORDER BY`. That is why `DefaultOrderBy` is not +optional in practice — and a page without a total order is a different set each +time it is asked for anyway, so the requirement is doing you a favour. + +--- + +## The whole table comes back under a request for a handful + +**An untranslated filter, on a splice caller that passed `RejectUnsupported = false`.** + +The splice caller's `WHERE` *is* `FilterSql`. If the filter could not be +translated and was dropped, there is no `WHERE`. Pass `true`: `Rejected` comes +back set, and the caller is expected to fail the request. + +`$orderby` is the one thing dropped rather than rejected. A wrong order is +cosmetic; a wrong row count is not. + +--- + +## The grammar is smaller than OData, deliberately + +Everything below is what a Mendix client actually emits. That set was not read +off the OData specification — Mendix's +[consumed OData service requirements](https://docs.mendix.com/refguide/consumed-odata-service-requirements/) +names the query options a service must support and **not one operator or +function**. It was captured off the wire instead: a running app driving real +datagrids with `OData Publish` at TRACE, plus fourteen XPath probe microflows to +force each shape. + +Anything outside it — arithmetic, lambdas, `$apply`, date functions, `any`/`all` +— is rejected rather than approximated. + +--- + +## Injection + +Column names come from the client and are resolved through the whitelist; +nothing else reaches the SQL. Literals are escaped (`'` → `''`), numerics must +parse as numbers, and a key must match `[A-Za-z0-9_.-]{1,128}` — keys are +usually interpolated by the caller rather than bound, so their *shape* is +whitelisted rather than their content escaped. + +`DefaultOrderBy` is spliced verbatim. It is yours, not the client's. + +--- + +## Dialects differ in exactly two places + +| | case-insensitive LIKE | page | +|---|---|---| +| postgresql, duckdb | `col ILIKE '%x%'` | `LIMIT n OFFSET m` | +| sqlserver, oracle | `LOWER(col) LIKE LOWER('%x%')` | `OFFSET m ROWS FETCH NEXT n ROWS ONLY` | +| mysql | `LOWER(col) LIKE LOWER('%x%')` | `LIMIT m, n` | + +--- + +## Testing the parser without a runtime + +`ODataQueryParser` is strings in, strings out, with no Mendix types in its +signature. It runs under `jshell` or a plain JUnit test with no runtime around +it, which is how the grammar above was checked term by term. Keep it that way: +the moment it takes an `IContext`, the cheap test disappears. diff --git a/.claude/skills/packs/mendix-odata-pushdown/references/packaging-gap.md b/.claude/skills/packs/mendix-odata-pushdown/references/packaging-gap.md new file mode 100644 index 000000000..5c7960474 --- /dev/null +++ b/.claude/skills/packs/mendix-odata-pushdown/references/packaging-gap.md @@ -0,0 +1,123 @@ +# Why `installs.java` exists + +*The gap this describes is closed — `installs.java` is implemented and this pack +uses it. Kept because it is the reasoning behind the target's shape, and the +next pack that wants a new one will need the same argument made.* + +This pack is the third one `docs/11-proposals/PROPOSAL_skill_packs.md` asks for: + +> A third is wanted (`mendix-odata-pushdown`, Java actions that push `$filter` / +> `$orderby` / `$top` / `$skip` into database-connector SQL) and there will be +> more. + +It was also the one that needed a target the mechanism did not have. + +## The shape of the problem + +The four Java actions are declared in `mdl/module.mdl` with inline bodies, which +mxcli writes out as `.java` files itself. But every body is a two-line +delegation: + +``` +AS $$ +return {{MODULE_PATH}}.QueryObject.parse(getContext(), Uri, Columns, Dialect, + MaxTop, DefaultTop, DefaultOrderBy, KeyField, RejectUnsupported); +$$; +``` + +The work is in three helper classes that MDL cannot author at all: + +| File | Lines | What it is | +|---|---|---| +| `java/ODataQueryParser.java` | 633 | the parser — strings in, strings out, no Mendix types | +| `java/RoutineCall.java` | 185 | stored-routine invocation, per engine | +| `java/QueryObject.java` | 64 | the binding — `Core.instantiate` and `setValue` | + +882 lines that have to land in `javasource/{{MODULE_PATH}}/`. Three independent +mechanisms say a pack cannot put them there: + +1. **`Installs` has two fields.** `cmd/mxcli/skillpack/skillpack.go`: + `Widgets []string` and `MDL []string`. There is no third. +2. **Pack files land inside the skills directory.** `Install` writes to + `destDir//`. Installing `mendix-bulk-oql-dml` puts all five of its + files under `.claude/skills/mendix-bulk-oql-dml/` and nothing outside it. +3. **MDL has no standalone-class form.** `createJavaActionStatement` in + `mdl/grammar/domains/MDLMicroflow.g4` accepts `AS DOLLAR_STRING` and nothing + else — a method body, no class declaration, no imports clause. + +So a pack **used to** ship the prose and the MDL, and the reader still copied a +directory by hand. **That is exactly the manual step the pack existed to +remove**, which is why it was worth fixing rather than working around. + +## The proposed target + +```yaml +installs: + java: + - java # -> javasource/{{MODULE_PATH}}/, preserving actions/ +``` + +The interesting part is that it needs the *same* discipline the widget path +already has, for the same reason. A widget id is its identity; a Java `package` +declaration is the exact analogue. Two projects whose classes share a package +are two projects claiming the same class, and the symptom is a compile error in +somebody else's module. + +The three properties the proposal already argues for transfer unchanged: + +- **Placeholders, not a real namespace.** These files ship as `{{MODULE_PATH}}`, + not `odatapushdown`. Leaving the harvested project's name in place means a bug + ships *their* namespace silently; an unsubstituted `{{MODULE_PATH}}` fails to + compile, loudly. +- **A whitelist, not a scan.** All eight files are named in `rewrite.files`. +- **Drift in either direction is an error** — a declared file with no token, or + a declared file the pack does not ship, refuses the install. + +One implementation question worth deciding rather than defaulting: `java/actions/` +is shipped here for review, but on `--apply` mxcli generates those four classes +from the MDL itself. Writing both means the pack's copy is overwritten +immediately. Skipping `actions/` when MDL is applied, and writing it when it is +not, is probably right — but it is a real branch, not an obvious one. + +## Rejected alternatives + +**Inline the helpers into the action bodies.** Java local classes cannot be +shared between methods, so 882 lines would be duplicated four times. The +one-fat-action-plus-microflow-wrappers variant avoids the duplication by +distorting the public API to fit the packaging, which is the wrong way round. + +**Ship the `.java` as inert assets plus a copy step in `SKILL.md`.** Works +today, and is what this pack does in the meantime. It gets none of the three +things that make a pack better than a tarball: no pruning when a file is +dropped in v2, no digest fence refusing a locally-edited file, no namespace +rewrite. + +## Applying it by hand, until then + +```bash +mxcli skill add mendix-odata-pushdown -p App.mpr # copies, does not apply + +# substitute the destination module's names yourself +cd .claude/skills/mendix-odata-pushdown +sed -i 's/{{MODULE}}/ODataPushdown/g; s/{{MODULE_PATH}}/odatapushdown/g' \ + mdl/module.mdl java/*.java java/actions/*.java + +mkdir -p /javasource/odatapushdown +cp java/*.java /javasource/odatapushdown/ +mxcli exec mdl/module.mdl -p /App.mpr +``` + +`java/actions/` can be skipped — `mxcli exec` generates those four classes from +the MDL. Then add `ODataPushdown.User` to whichever user roles run your read +microflows. + +## One more thing the proposal already flags + +> **Verifying a pack in CI.** [...] A pack whose own verifier is not run in CI +> is a pack that rots. + +This pack wants that more than the other two. It is 882 lines of parser across +five dialects, and it has a real test surface: `ODataQueryParser` takes no +Mendix types, so it runs under `jshell` or JUnit with no runtime. A +`verify:` script that exercises the grammar term by term would be cheap and +would catch a dialect regression that no `mx check` can see. diff --git a/.claude/skills/packs/mendix-odata-pushdown/references/patterns.md b/.claude/skills/packs/mendix-odata-pushdown/references/patterns.md new file mode 100644 index 000000000..08ee4d772 --- /dev/null +++ b/.claude/skills/packs/mendix-odata-pushdown/references/patterns.md @@ -0,0 +1,127 @@ +# The two patterns, end to end + +Which one applies is decided by one question: **do you own the SQL?** + +--- + +## Splice — you build the statement + +Concatenate the fragments. One parse, one statement, everything pushed down. + +``` +$Q = CALL JAVA ACTION {{MODULE}}.Parse( + Uri = $Request/Uri, + Columns = 'name:d.name:text,wins:d.race_wins:number,born:d.dob:date', + Dialect = 'postgresql', + MaxTop = 500, + DefaultTop = 500, + DefaultOrderBy = 'd.name ASC', + KeyField = 'driverId', + RejectUnsupported = true); + +IF $Q/Rejected THEN + -- fail the request; do not answer it with unfiltered rows +END + +DECLARE $Sql String = 'SELECT d.* FROM drivers d' + $Q/FilterSql + $Q/OrderBySql; +``` + +**Splice callers should pass `RejectUnsupported = true`.** Their `WHERE` *is* +`FilterSql`, so an untranslated filter means no `WHERE` at all — every row in +the table, under a 200, in answer to a request for a handful. + +--- + +## Bind — the SQL is somebody else's + +A named query on a database connection, a view, a procedure. Take the values and +pass them as parameters; nothing of yours reaches the far side as text. + +``` +$Rows = execute database query MyMod.Warehouse.GetSeasons + (keyFilter = $Q/Key, + topN = toString($Q/Top), + skipN = toString($Q/Skip), + sortCol = $Q/SortColumn1, + sortDir = $Q/SortDirection1); +``` + +Bind callers can pass `RejectUnsupported = false`: they never look at +`FilterSql`, so failing a request over a filter they were never going to apply +trades one wrong answer for another. + +For a bind-style caller the column map is usually `name:name:type`, because the +sort travels as the exposed name the query's own `CASE` matches on. + +### Making a fixed statement sortable by a parameter + +The statement cannot be rewritten, so the ordering has to be data. Wrap the real +query and drive both direction and column from bound values: + +```sql +SELECT * FROM ( ) t +WHERE {keyFilter} = '' OR CAST(t.id AS VARCHAR) = {keyFilter} +ORDER BY + CASE WHEN {sortDir} = 'A' THEN (CASE {sortCol} WHEN 'name' THEN t.name END) END ASC NULLS LAST, + CASE WHEN {sortDir} = 'D' THEN (CASE {sortCol} WHEN 'name' THEN t.name END) END DESC NULLS LAST, + +LIMIT CAST({topN} AS BIGINT) OFFSET CAST({skipN} AS BIGINT) +``` + +One `CASE` arm per sortable column. The `{keyFilter} = '' OR …` shape is what +makes the same statement serve both the collection and the single-row re-read. + +--- + +## The short forms + +Plenty of resources are reachable one way only — the sessions of this weekend, +the laps of this race — and their entire contract is one value out of `$filter`. +Making those declare a column map and a dialect to reach one string is a tax on +the common case. + +``` +$Key = CALL JAVA ACTION {{MODULE}}.Key(Uri = $Request/Uri, KeyField = 'raceId'); +$Year = CALL JAVA ACTION {{MODULE}}.FilterNumber(Uri = $Request/Uri, Field = 'year', Fallback = 0); +``` + +**They are not interchangeable.** `Key` falls back to the path segment +(`/Res('v')`), because that is how a client re-reads one row. `FilterNumber` +must not: reading `1036` out of `/Calendar('1036-c')` as if it were a year +answers a question nobody asked, which is the failure mode the module exists to +stop. + +Guard the comparison — an unset number thrown at `>` fails at render time in the +browser, not in `mx check`: + +``` +IF $Year != empty AND $Year > 0 THEN … +``` + +--- + +## Stored routines + +`CallStatement` renders the invocation for the engine you are on and never +renders a value: + +``` +CallStatement('f1ops.driver_form', 'table', 'driverId,lastN', 'postgresql') + -> SELECT * FROM f1ops.driver_form({driverId}, {lastN}) +``` + +`Parameters` is a comma-separated list of **Mendix query-parameter names**, in +the routine's own argument order. What comes back is a template full of +`{placeholders}` for `execute database query` to bind. The literal `null` passes +through as SQL `NULL`, which is how a Postgres procedure's INOUT slots are +filled. + +This is a stronger position than the `$filter` translation can take. A `WHERE` +clause must be built as text because its *shape* comes from the client; a +routine call's shape is fixed by the routine and only its values vary. The only +text emitted is the routine name, checked against an identifier pattern rather +than escaped. + +| `Kind` | postgresql / duckdb | sqlserver | oracle | mysql | +|---|---|---|---|---| +| `table` | `SELECT * FROM f(a,b)` | `SELECT * FROM f(a,b)` | `SELECT * FROM TABLE(f(a,b))` | refused — MySQL has none | diff --git a/Makefile b/Makefile index b11c86226..0a52fae45 100644 --- a/Makefile +++ b/Makefile @@ -237,9 +237,18 @@ check-skill-mdl: build @# The script above checks fenced blocks in markdown. A pack also ships real @# .mdl files, which it does not see — and a pack whose own MDL is never @# checked is a pack that rots. + @# + @# Checked AFTER substitution, because that is the only form anyone runs. A + @# pack's MDL may carry {{MODULE}} placeholders, which are not valid MDL and + @# never reach a project un-substituted; checking the raw file would fail on + @# every tokenised pack and tempt whoever hit it to drop the check instead. @for f in .claude/skills/packs/*/mdl/*.mdl; do \ [ -e "$$f" ] || continue; \ - ./$(BUILD_DIR)/$(BINARY_NAME) check "$$f" >/dev/null || { echo "FAILED: $$f"; exit 1; }; \ + tmp=$$(mktemp /tmp/skillmdl-XXXXXX.mdl); \ + sed -e 's/{{MODULE_PATH}}/mymodule/g' -e 's/{{MODULE}}/MyModule/g' \ + -e 's/{{NAMESPACE_PATH}}/acme/g' -e 's/{{NAMESPACE}}/acme/g' "$$f" > "$$tmp"; \ + ./$(BUILD_DIR)/$(BINARY_NAME) check "$$tmp" >/dev/null || { echo "FAILED: $$f"; rm -f "$$tmp"; exit 1; }; \ + rm -f "$$tmp"; \ echo " ok $$f"; \ done @./scripts/check-skill-mdl.sh ./$(BUILD_DIR)/$(BINARY_NAME) docs-site/src diff --git a/cmd/mxcli/cmd_skill.go b/cmd/mxcli/cmd_skill.go index a04508a8a..7dd82c2dd 100644 --- a/cmd/mxcli/cmd_skill.go +++ b/cmd/mxcli/cmd_skill.go @@ -17,6 +17,7 @@ var ( skillPackDir string skillPackNamespace string skillPackProject string + skillPackModule string ) // packsFS returns the embedded packs rooted at the pack directory, so callers @@ -109,8 +110,27 @@ var skillAddCmd = &cobra.Command{ if err := os.MkdirAll(dir, 0o755); err != nil { return err } - opts := skillpack.Options{} - var ns string + opts := skillpack.Options{Vars: map[string]string{}} + var ns, mod string + + // A pack that places Java needs the owning Mendix module before + // anything is written: the module name is baked into every `package` + // line, and a class placed under the wrong one does not compile. + if pack.NeedsModule() { + if skillPackModule == "" { + return fmt.Errorf("pack %q places Java into a Mendix module, whose name every\n"+ + "`package` line carries. Pass --module (e.g. --module ODataPushdown)", pack.Name) + } + vars, err := skillpack.ModuleVars(skillPackModule) + if err != nil { + return err + } + for k, v := range vars { + opts.Vars[k] = v + } + mod = vars["MODULE"] + } + if pack.NeedsNamespace() { ns, err = resolveNamespace() if err != nil { @@ -131,7 +151,12 @@ var skillAddCmd = &cobra.Command{ rel = r } } - opts.Vars = skillpack.Vars(ns, filepath.ToSlash(rel)) + for k, v := range skillpack.Vars(ns, filepath.ToSlash(rel)) { + opts.Vars[k] = v + } + } + if len(opts.Vars) == 0 { + opts.Vars = nil // let the lock supply them on an upgrade } res, err := skillpack.InstallWith(fsys, pack.Name, dir, opts) @@ -166,6 +191,33 @@ var skillAddCmd = &cobra.Command{ fmt.Println("\nThen let mxcli see it:\n mxcli widget init -p .mpr") } + if mod != "" { + jres, err := skillpack.InstallJava(fsys, pack.Name, projectDirForPack(), opts) + if err != nil { + return err + } + fmt.Printf("\nModule: %s\n", mod) + switch { + case len(jres.Written) > 0: + fmt.Printf(" %d Java file(s) placed in %s/\n", len(jres.Written), jres.Dest) + case len(jres.Skipped) > 0 && len(jres.Refused) == 0: + fmt.Printf(" %s/ is already up to date\n", jres.Dest) + } + // Refusing is the whole point of the guard, so it is reported first + // and by name — a count alone leaves the reader unable to act. + if len(jres.Refused) > 0 { + fmt.Printf(" REFUSED (present and different, left alone):\n") + for _, f := range jres.Refused { + fmt.Printf(" %s\n", filepath.Join(jres.Dest, f)) + } + fmt.Println(" Compare and delete the ones you want replaced, then re-run.") + } + if len(jres.Excluded) > 0 { + fmt.Printf(" %d action class(es) not placed — mxcli generates those from the MDL\n", + len(jres.Excluded)) + } + } + // Copying the pack never touches the model. Anything that would is // reported as a next step the user runs deliberately — a documentation // install that silently added Java actions to the .mpr would be exactly @@ -176,7 +228,12 @@ var skillAddCmd = &cobra.Command{ fmt.Printf(" review then apply: mxcli exec %s -p .mpr\n", filepath.Join(dir, pack.Name, filepath.FromSlash(m))) } - fmt.Println(" (the MDL uses a MyModule placeholder — set the target module first)") + // Only true of a pack whose MDL was NOT substituted. Saying it of + // one that just had its real module name written in sends the + // reader looking for a placeholder that is not there. + if !mdlWasSubstituted(pack) { + fmt.Println(" (the MDL uses a MyModule placeholder — set the target module first)") + } } return nil }, @@ -250,6 +307,8 @@ func init() { "Widget namespace for packs that ship a widget (default: derived from the project name)") skillAddCmd.Flags().StringVarP(&skillPackProject, "project", "p", "", "Path to the .mpr the pack is being installed for") + skillAddCmd.Flags().StringVar(&skillPackModule, "module", "", + "Mendix module that will own a pack's Java (e.g. ODataPushdown)") skillCmd.AddCommand(skillListCmd, skillAddCmd, skillRemoveCmd, skillUpgradeCmd) rootCmd.AddCommand(skillCmd) } @@ -290,3 +349,16 @@ func projectDirForPack() string { } return filepath.Dir(mpr) } + +// mdlWasSubstituted reports whether the pack's MDL carries tokens the install +// filled in, which decides whether the reader still has a placeholder to edit. +func mdlWasSubstituted(pack skillpack.Pack) bool { + for _, m := range pack.Installs.MDL { + for _, r := range pack.Rewrite.Files { + if filepath.ToSlash(r) == filepath.ToSlash(m) { + return true + } + } + } + return false +} diff --git a/cmd/mxcli/skillpack/java.go b/cmd/mxcli/skillpack/java.go new file mode 100644 index 000000000..c93b2349f --- /dev/null +++ b/cmd/mxcli/skillpack/java.go @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: Apache-2.0 + +package skillpack + +import ( + "fmt" + "io/fs" + "os" + "path" + "path/filepath" + "regexp" + "sort" + "strings" +) + +// A Java package declaration is a class's identity, exactly as a widget id is: +// two projects whose classes share a package are two projects claiming the same +// class, and the symptom is a compile error inside somebody else's module. So +// Java ships with {{MODULE}} / {{MODULE_PATH}} placeholders and `skill add` +// substitutes the destination module's names, on the same three rules the widget +// path already follows — placeholders rather than a real name, a whitelist +// rather than a scan, and drift in either direction refusing the install. + +// GeneratedActionsDir is the one subdirectory of a pack's Java that is NOT +// placed. +// +// mxcli writes the action classes itself from the MDL's `CREATE JAVA ACTION … +// AS $$ … $$` bodies, so placing the pack's copies too means two sources of +// truth for the same four files, and applying the MDL immediately overwrites +// what the pack just wrote. They stay in the pack directory to be read. +const GeneratedActionsDir = "actions" + +var moduleNameInvalid = regexp.MustCompile(`[^A-Za-z0-9_]+`) + +// NormalizeModule validates a Mendix module name and derives its javasource +// package segment. +// +// Mendix lowercases the module name for the package, which is why the two are +// derived from one value here rather than asked for separately — a pack whose +// `package` line and directory disagree does not compile, and the error names +// neither. +func NormalizeModule(in string) (name, pkg string, err error) { + name = moduleNameInvalid.ReplaceAllString(strings.TrimSpace(in), "") + if name == "" { + return "", "", fmt.Errorf("module name %q has no usable characters; "+ + "pass --module with a Mendix module name like ODataPushdown", in) + } + if name[0] >= '0' && name[0] <= '9' { + return "", "", fmt.Errorf("module name %q starts with a digit, which a Java package cannot", in) + } + return name, strings.ToLower(name), nil +} + +// ModuleVars returns the substitution values for a pack that places Java. +func ModuleVars(module string) (map[string]string, error) { + name, pkg, err := NormalizeModule(module) + if err != nil { + return nil, err + } + return map[string]string{"MODULE": name, "MODULE_PATH": pkg}, nil +} + +// JavaResult reports what placing a pack's Java did. +type JavaResult struct { + Dest string // javasource/, for reporting + Written []string // paths written, relative to Dest + Skipped []string // already byte-identical + Refused []string // present and different — left alone + Excluded []string // shipped but deliberately not placed (generated actions) +} + +// Changed reports whether anything moved on disk. +func (r JavaResult) Changed() bool { return len(r.Written) > 0 } + +// InstallJava places a pack's Java sources into projectDir/javasource//. +// +// A file that already exists and differs is REFUSED, never overwritten. This is +// the guard-don't-drop rule the theme package already follows (ADR-0005): from +// the outside, a locally fixed helper and a stale copy look identical, and +// silently replacing 882 lines of somebody's edited parser is not a trade this +// should make on their behalf. The refusal names the files so the choice stays +// with whoever knows which side is right. +func InstallJava(fsys fs.FS, name, projectDir string, opts Options) (JavaResult, error) { + pack, err := Load(fsys, name) + if err != nil { + return JavaResult{}, err + } + var res JavaResult + if len(pack.Installs.Java) == 0 { + return res, nil + } + pkg := opts.Vars["MODULE_PATH"] + if pkg == "" { + return res, fmt.Errorf("pack %q places Java, which needs the owning Mendix module; pass --module", name) + } + + rewrites := map[string]bool{} + for _, f := range pack.Rewrite.Files { + rewrites[filepath.ToSlash(f)] = true + } + + dest := filepath.Join(projectDir, "javasource", pkg) + res.Dest = filepath.Join("javasource", pkg) + + for _, dir := range pack.Installs.Java { + root := path.Join(pack.Dir, filepath.ToSlash(dir)) + if _, err := fs.Stat(fsys, root); err != nil { + return res, fmt.Errorf("pack %q: installs.java names %q, which the pack does not ship: %w", name, dir, err) + } + err := fs.WalkDir(fsys, root, func(p string, d fs.DirEntry, err error) error { + if err != nil || d.IsDir() { + return err + } + rel, err := filepath.Rel(root, p) + if err != nil { + return err + } + rel = filepath.ToSlash(rel) + + // The MDL owns these; see GeneratedActionsDir. + if top, _, _ := strings.Cut(rel, "/"); top == GeneratedActionsDir { + res.Excluded = append(res.Excluded, rel) + return nil + } + + want, err := fs.ReadFile(fsys, p) + if err != nil { + return err + } + // Declared relative to the PACK root, which is where the manifest + // speaks from — not relative to the java directory being walked. + if packRel := path.Join(filepath.ToSlash(dir), rel); rewrites[packRel] { + if want, err = substitute(packRel, want, opts.Vars); err != nil { + return err + } + } + + dst := filepath.Join(dest, filepath.FromSlash(rel)) + if have, readErr := os.ReadFile(dst); readErr == nil { + if string(have) == string(want) { + res.Skipped = append(res.Skipped, rel) + } else { + res.Refused = append(res.Refused, rel) + } + return nil + } + if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { + return err + } + if err := os.WriteFile(dst, want, 0o644); err != nil { + return err + } + res.Written = append(res.Written, rel) + return nil + }) + if err != nil { + return res, fmt.Errorf("placing pack %q Java: %w", name, err) + } + } + sort.Strings(res.Written) + sort.Strings(res.Skipped) + sort.Strings(res.Refused) + sort.Strings(res.Excluded) + return res, nil +} diff --git a/cmd/mxcli/skillpack/java_test.go b/cmd/mxcli/skillpack/java_test.go new file mode 100644 index 000000000..c0336ee25 --- /dev/null +++ b/cmd/mxcli/skillpack/java_test.go @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: Apache-2.0 + +package skillpack + +import ( + "os" + "path/filepath" + "strings" + "testing" + "testing/fstest" +) + +const javaManifest = `name: jv-pack +version: 1.0.0 +rewrite: + files: + - java/Helper.java + - java/actions/Do.java +installs: + java: + - java +` + +func javaFS() fstest.MapFS { + return fstest.MapFS{ + "jv-pack/pack.yaml": {Data: []byte(javaManifest)}, + "jv-pack/SKILL.md": {Data: []byte("# jv\n")}, + "jv-pack/java/Helper.java": {Data: []byte( + "package {{MODULE_PATH}};\npublic class Helper { static String m = \"{{MODULE}}\"; }\n")}, + "jv-pack/java/sub/Deep.java": {Data: []byte("package x.sub;\n")}, + "jv-pack/java/actions/Do.java": {Data: []byte( + "package {{MODULE_PATH}}.actions;\n// generated from the MDL\n")}, + } +} + +// TestInstallJavaPlacesIntoJavasource is the headline: a helper class only +// compiles where the module expects it, which is outside the pack's own +// directory — the one thing no other install target does. +func TestInstallJavaPlacesIntoJavasource(t *testing.T) { + proj := t.TempDir() + vars, err := ModuleVars("ODataPushdown") + if err != nil { + t.Fatal(err) + } + res, err := InstallJava(javaFS(), "jv-pack", proj, Options{Vars: vars}) + if err != nil { + t.Fatalf("InstallJava: %v", err) + } + + body := readFile(t, proj, "javasource/odatapushdown/Helper.java") + if !strings.Contains(body, "package odatapushdown;") { + t.Errorf("package not substituted: %s", body) + } + if !strings.Contains(body, `"ODataPushdown"`) { + t.Errorf("{{MODULE}} not substituted: %s", body) + } + // Subdirectories are preserved — a flattened package does not compile. + if _, err := os.Stat(filepath.Join(proj, "javasource/odatapushdown/sub/Deep.java")); err != nil { + t.Errorf("subdirectory not preserved: %v", err) + } + if len(res.Written) != 2 { + t.Errorf("wrote %v, want the two non-action files", res.Written) + } +} + +// TestInstallJavaExcludesGeneratedActions — mxcli writes the action classes +// from the MDL, so placing the pack's copies too means two sources of truth for +// the same files and applying the MDL immediately overwrites them. +func TestInstallJavaExcludesGeneratedActions(t *testing.T) { + proj := t.TempDir() + vars, _ := ModuleVars("ODataPushdown") + res, err := InstallJava(javaFS(), "jv-pack", proj, Options{Vars: vars}) + if err != nil { + t.Fatalf("InstallJava: %v", err) + } + if _, err := os.Stat(filepath.Join(proj, "javasource/odatapushdown/actions/Do.java")); err == nil { + t.Error("an action class was placed; the MDL owns those") + } + if len(res.Excluded) != 1 { + t.Errorf("Excluded = %v, want the one action class reported", res.Excluded) + } +} + +// TestInstallJavaRefusesToClobber is the guard-don't-drop rule (ADR-0005). From +// the outside a locally fixed helper and a stale copy look identical, so the +// choice stays with whoever knows which side is right. +func TestInstallJavaRefusesToClobber(t *testing.T) { + proj := t.TempDir() + vars, _ := ModuleVars("ODataPushdown") + if _, err := InstallJava(javaFS(), "jv-pack", proj, Options{Vars: vars}); err != nil { + t.Fatalf("first install: %v", err) + } + + edited := filepath.Join(proj, "javasource/odatapushdown/Helper.java") + if err := os.WriteFile(edited, []byte("package odatapushdown;\n// my fix\n"), 0o644); err != nil { + t.Fatal(err) + } + res, err := InstallJava(javaFS(), "jv-pack", proj, Options{Vars: vars}) + if err != nil { + t.Fatalf("second install: %v", err) + } + if len(res.Refused) != 1 || !strings.Contains(res.Refused[0], "Helper.java") { + t.Errorf("Refused = %v, want the edited file named", res.Refused) + } + body := readFile(t, proj, "javasource/odatapushdown/Helper.java") + if !strings.Contains(body, "// my fix") { + t.Error("the local edit was overwritten") + } +} + +// TestInstallJavaIsIdempotent — an unchanged re-install must not churn files, +// same as every other write path in this repo (ADR-0008). +func TestInstallJavaIsIdempotent(t *testing.T) { + proj := t.TempDir() + vars, _ := ModuleVars("ODataPushdown") + if _, err := InstallJava(javaFS(), "jv-pack", proj, Options{Vars: vars}); err != nil { + t.Fatalf("first: %v", err) + } + res, err := InstallJava(javaFS(), "jv-pack", proj, Options{Vars: vars}) + if err != nil { + t.Fatalf("second: %v", err) + } + if res.Changed() || len(res.Refused) > 0 { + t.Errorf("re-install churned: written=%v refused=%v", res.Written, res.Refused) + } + if len(res.Skipped) != 2 { + t.Errorf("Skipped = %v, want both files", res.Skipped) + } +} + +// TestInstallJavaNeedsAModule — placing a class without knowing the module +// would write a package line that cannot be right. +func TestInstallJavaNeedsAModule(t *testing.T) { + if _, err := InstallJava(javaFS(), "jv-pack", t.TempDir(), Options{}); err == nil { + t.Error("Java was placed with no module") + } +} + +// TestInstallJavaRefusesUndeclaredDirectory — a manifest naming a directory the +// pack does not ship placed nothing while reporting success. +func TestInstallJavaRefusesUndeclaredDirectory(t *testing.T) { + fsys := javaFS() + fsys["jv-pack/pack.yaml"] = &fstest.MapFile{Data: []byte( + "name: jv-pack\nversion: 1.0.0\ninstalls:\n java:\n - nosuchdir\n")} + vars, _ := ModuleVars("M") + if _, err := InstallJava(fsys, "jv-pack", t.TempDir(), Options{Vars: vars}); err == nil { + t.Error("installs.java naming a missing directory was accepted") + } +} + +func TestNormalizeModule(t *testing.T) { + cases := map[string][2]string{ + "ODataPushdown": {"ODataPushdown", "odatapushdown"}, + "My Module": {"MyModule", "mymodule"}, + "Data_Warehouse": {"Data_Warehouse", "data_warehouse"}, + } + for in, want := range cases { + name, pkg, err := NormalizeModule(in) + if err != nil { + t.Errorf("NormalizeModule(%q): %v", in, err) + continue + } + if name != want[0] || pkg != want[1] { + t.Errorf("NormalizeModule(%q) = %q/%q, want %q/%q", in, name, pkg, want[0], want[1]) + } + } + for _, bad := range []string{"", " ", "9Module", "---"} { + if _, _, err := NormalizeModule(bad); err == nil { + t.Errorf("NormalizeModule(%q) was accepted", bad) + } + } +} diff --git a/cmd/mxcli/skillpack/skillpack.go b/cmd/mxcli/skillpack/skillpack.go index ccaae40d4..52fe19580 100644 --- a/cmd/mxcli/skillpack/skillpack.go +++ b/cmd/mxcli/skillpack/skillpack.go @@ -85,6 +85,19 @@ type Rewrite struct { type Installs struct { Widgets []string `yaml:"widgets"` MDL []string `yaml:"mdl"` + + // Java names directories of plain Java the pack must place in the project's + // javasource/ tree. It is the one target that writes OUTSIDE the pack's own + // directory, because a helper class only compiles where the module expects + // it — everything else a pack ships is documentation the reader opens where + // it lands. + // + // This exists because MDL cannot author a standalone class: a Java action + // body is a method body, with no class declaration and no imports. A pack + // whose actions delegate into helper classes can otherwise ship only the + // prose telling somebody to copy a directory by hand, which is the manual + // step the pack existed to remove. + Java []string `yaml:"java"` } // Pack is a manifest plus the directory it was read from. @@ -97,9 +110,18 @@ type Pack struct { // modify the .mpr. Callers use it to decide whether to demand confirmation. func (p Pack) WritesToModel() bool { return len(p.Installs.MDL) > 0 } -// NeedsNamespace reports whether this pack carries files to substitute, and so -// cannot be installed without knowing the destination project. -func (p Pack) NeedsNamespace() bool { return len(p.Rewrite.Files) > 0 } +// NeedsNamespace reports whether this pack ships a widget, whose id must carry +// the destination project's namespace. +// +// Keyed on the widget, not on rewrite.files: a pack can have plenty to +// substitute and no widget at all — the Java pack tokenises eight files and +// wants a MODULE, never a NAMESPACE. Asking for the wrong one is not a harmless +// extra prompt; it invites an answer that then goes nowhere. +func (p Pack) NeedsNamespace() bool { return len(p.Installs.Widgets) > 0 } + +// NeedsModule reports whether this pack places Java, which cannot be done +// without knowing the Mendix module that will own it. +func (p Pack) NeedsModule() bool { return len(p.Installs.Java) > 0 } // Options carries what a pack needs to know about the destination. type Options struct { diff --git a/cmd/mxcli/skillpacks_test.go b/cmd/mxcli/skillpacks_test.go index 3d4e20c2b..cdf373592 100644 --- a/cmd/mxcli/skillpacks_test.go +++ b/cmd/mxcli/skillpacks_test.go @@ -61,6 +61,11 @@ func TestVendoredPacksLoad(t *testing.T) { t.Errorf("installs.mdl names %s, which is not shipped: %v", m, err) } } + for _, j := range p.Installs.Java { + if _, err := fs.Stat(fsys, p.Dir+"/"+j); err != nil { + t.Errorf("installs.java names %s, which is not shipped: %v", j, err) + } + } }) } } diff --git a/docs/11-proposals/PROPOSAL_skill_packs.md b/docs/11-proposals/PROPOSAL_skill_packs.md index 49b5ea0f5..019794e88 100644 --- a/docs/11-proposals/PROPOSAL_skill_packs.md +++ b/docs/11-proposals/PROPOSAL_skill_packs.md @@ -167,6 +167,35 @@ the namespace has to be right *before* the build, so shipping a prebuilt package would mean rewriting paths inside a zip and hoping, where rewriting source is the path the ledger verified. +### A pack can place Java, and only Java, outside its own directory + +`installs.java` is the third target and the only one that writes outside +`.claude/skills//`. That is not a convenience: MDL cannot author a +standalone class — `createJavaActionStatement` accepts a **method body**, no +class declaration and no imports — so a pack whose actions delegate into helper +classes could ship only prose telling somebody to copy a directory by hand, +which is the manual step packs exist to remove. + +A Java `package` is a class's identity exactly as a widget id is, so it reuses +the substitution machinery unchanged: `{{MODULE}}` and `{{MODULE_PATH}}`, +declared in `rewrite.files`, supplied by `--module`. + +Three rules, each of which had a wrong default available: + +1. **`java/actions/` is not placed.** mxcli writes those classes itself from the + MDL, so placing the pack's copies means two sources of truth for the same + files and applying the MDL overwrites them immediately. They stay in the pack + to be read. +2. **An existing file that differs is refused, never overwritten** — guard-don't-drop + ([ADR-0005](../13-decisions/0005-semantic-model-interface-currency.md)). A + locally fixed helper and a stale copy are indistinguishable from here, and + silently replacing somebody's edited parser is not a trade to make on their + behalf. The refusal names the files. +3. **A namespace is a widget question, a module is a Java one.** `NeedsNamespace` + keys on `installs.widgets`, not on `rewrite.files` — the Java pack tokenises + eight files and wants a `MODULE`, never a `NAMESPACE`. Asking for the wrong + one invites an answer that then goes nowhere. + ### Manifest ```yaml @@ -218,6 +247,7 @@ pack that rots. | 3 | Vendor `mendix-bulk-oql-dml` (no widget, so no namespace question), wire its MDL into `make check-skill-mdl`. | | 4 | Vendor `mendix-vega-charts` with install-time namespace substitution. | | 5 | Run `check-spec.mjs` over the shipped specs in CI. | +| 6 | `installs.java` + vendor `mendix-odata-pushdown`, the third pack. | Slice 1 is worth landing on its own: it removes the silent-flattening hazard in the write path whether or not any pack ever ships. From 9526e43ba85cd47e68b7db3cedd5d895b601df4b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 20:46:42 +0000 Subject: [PATCH 12/22] Explain the OData property whose name invites the wrong value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ledger finding #113 reported a published service failing with CE7375 and concluded mxcli was missing an "association representation" property. It is not missing. There is no such property to add, and the value the script gave an existing one is what cannot build. Established before writing anything: - "Representation" appears nowhere in the Mendix Model SDK (4.114.0, the latest published) and nowhere in Mendix 11.13's own metamodel assemblies. The only service property is PublishAssociations. - Studio Pro's two labels for it, read out of Mendix.Modeler.Localization.dll, are "As a link (recommended)" and "As an associated object id" — the true and false of that boolean. So it is a two-value representation wearing the name of a yes/no. A service publishing no associations reads `No` as obviously correct and gets an error naming a concept its script never mentions. Measured on 11.13, control and treatment, same service on a blank app: PublishAssociations: No -> CE7375 PublishAssociations: Yes -> 0 errors mxcli already defaults it to Yes and accepts it on CREATE, so nothing is broken in the writer — what was missing is anything that says so. MDL-ODATA06 warns, naming CE7375 and the fix. It stays a warning, not an error: false is a legitimate Mendix mode for a service whose key is arranged in Studio Pro; what it is not is what it sounds like. The same probe found a second shape mxcli writes happily. mxbuild wants a location with no leading slash that ends in a single slash, and with NO slash at all its own validator throws: Path 'cat' -> System.ArgumentOutOfRangeException, no error code Path '/cat/' -> CE6550 "The path should not start with a slash." Path 'odata/cat' -> CE6552 "The location should end with a single slash." Path 'odata/cat/' -> 0 errors The crash is the reason this is worth a rule: there is no code to look up and no element named, so a one-character mistake reads as a corrupt project. MDL-ODATA05 catches all three. It found a real one immediately: 595-published-odata-entitytypepointer.mdl publishes `/odata/customers`, which is CE6550 — an example that had only ever been `mxcli check`ed, never built. Fixed here. The odata-data-sharing skill said to keep the default, but scoped it to non-persistable entities, which implies a persistable one is fine with No. It is not: a persistable entity with a unique key of its own fails identically. Corrected with the measurement. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4 --- .claude/skills/fix-issue.md | 2 + .claude/skills/mendix/odata-data-sharing.md | 28 +++- cmd/mxcli/cmd_check.go | 7 + .../595-published-odata-entitytypepointer.mdl | 2 +- .../ledger-113-odata-service-shape.mdl | 55 ++++++++ mdl/executor/validate_odata_service_shape.go | 124 +++++++++++++++++ .../validate_odata_service_shape_test.go | 128 ++++++++++++++++++ 7 files changed, 343 insertions(+), 3 deletions(-) create mode 100644 mdl-examples/bug-tests/ledger-113-odata-service-shape.mdl create mode 100644 mdl/executor/validate_odata_service_shape.go create mode 100644 mdl/executor/validate_odata_service_shape_test.go diff --git a/.claude/skills/fix-issue.md b/.claude/skills/fix-issue.md index 38f6d0159..ca969a1f2 100644 --- a/.claude/skills/fix-issue.md +++ b/.claude/skills/fix-issue.md @@ -522,3 +522,5 @@ extracting `OffsetExpression`/`LimitExpression`. | `mxcli test` reports **PASS for an assertion that must fail** — `@expect 1 = 2`, `@expect length($result) = 999`, `@expect find($result, 'Z') >= 0` with the needle absent. Nothing in the output distinguishes a real assertion from a vacuous one, so a suite certifies work as verified while asserting only that the microflow did not throw. Mutation testing is what exposes it: mutants that return an obviously wrong value survive the suite | The `@expect` annotation was matched with a regex for one shape — `@expect $var (=|<>) ` — and `FindStringSubmatch` returning nil produced **no assertion at all** rather than an error. A test with zero assertions passes if its body completes. So the narrow support was not the defect; the silence was | `cmd/mxcli/testrunner/parser.go` (`expectPattern`, `parseAnnotations`), `cmd/mxcli/testrunner/expect.go` (new — `ParseExpect`, the validating parser), `cmd/mxcli/testrunner/generator_endpoint.go` + `generator.go` (emit the condition, not a rebuilt equality), `cmd/mxcli/testrunner/results.go` (`expectErrorResult`) | Capture the **whole** annotation body and hand it to a validating parser; anything it cannot compile becomes an `ExpectErrors` entry, the test is not generated at all, and the runner reports `StatusError` (which `FailCount` counts, so the exit code is non-zero). The parser is a strict recursive-descent pass over `exprcheck.Lex` — **not** `mdl/exprcheck`'s own parser, which recovers and emits hints, exactly the wrong behaviour here. Two measurements pinned the emitted expression against mxbuild 11.6.6: `<>` really is CE0117 (so the rewrite to `!=` is load-bearing, not cosmetic) and a wrong-typed comparison really is caught (`$result = 3` → CE0117), which is what makes the 0-error run on the 11 generated shapes mean something. **Generalisable**: when a pattern-matching parser can match *less* than its input, the non-match branch is a silent-failure path — audit every `if m := re.FindStringSubmatch(...); m != nil` whose else-branch does nothing. Repro `mdl-examples/bug-tests/expect-vacuous-assertions.mdl`. mxcli-sudoku FINDINGS #46 | | A test suite's green is unreadable: a test that asserts **nothing** prints the same `PASS` as one with six assertions, and `@verify` — documented as an OQL post-condition — is parsed and evaluated by nothing at all. After @expect started failing closed, the cheapest way back to green is to delete the assertion, and the output cannot tell that apart from a repair | Two silent-absence paths rather than the silent-drop path fixed in the row above. `TestResult` carried no assertion count, so nothing downstream could report one; and `TestCase.Verify` was populated by `parseAnnotations` and read by nothing but `--list` — `grep -n '\.Verify' cmd/mxcli/testrunner/*.go` returns the parser and the lister, no runner | `cmd/mxcli/testrunner/results.go` (`TestResult.Assertions`/`SourceFile`, `newResult`, `vacuousResult`, `resultNote`, `VacuousCount`), `cmd/mxcli/testrunner/parser.go` (`AssertionCount`, `AssertionErrors`, the @verify rejection), `cmd/mxcli/testrunner/junit.go` (`junitClassName`, assertions property), `cmd/mxcli/main.go` + `cmd_test_run.go` (`--require-assertions`) | Count assertions on the test case and carry them onto every result through **one** constructor (`newResult`) — the previous code built `TestResult` literals at five sites, which is exactly how a new field gets populated in one path and silently missing in another. Report the count on the ordinary result line, not behind `--verbose`: the whole lesson of #46 is that the *default* output must distinguish a test that asserted from one that did not. Vacuous tests warn by default and error under `--require-assertions`, because a smoke test is legitimate but an indistinguishable one is not. **Generalisable**: when auditing an annotation/config field for dead ends, grep for *readers*, not writers — a field with a parser and no consumer is a feature the docs promise and the code does not deliver, and it fails silently by construction. mxcli-sudoku FINDINGS #46 (follow-up) | | Windows Defender flags the mxcli **Windows** release binary as `Trojan:Script/Sabsik.EN.A!ml`; enterprise EDR (Defender for Endpoint, CrowdStrike, SentinelOne) blocks it harder. Not the generic unsigned-Go-binary false positive of #185 | The binary genuinely embedded **chisel**, a dual-use tunnelling/pivoting tool (SSH over WebSocket), on every platform — although the tunnel only ever runs inside a Linux container. `run --hub` linked `chisel/client`, `tunnel-hub` linked `chisel/server`, so windows/darwin carried 32 packages incl. the whole `x/crypto/ssh` stack for a feature they cannot use | `cmd/mxcli/docker/tunnel_linux.go` + `tunnel_other.go` (client seam), `cmd/mxcli/tunnelhub/control_linux.go` + `control_other.go` (server seam), `scripts/check-tunnel-deps.sh` (guard) | **Never obfuscate, pack or rename to dodge the scanner** — attacker tradecraft, and it makes the binary less trustworthy, not more. **Code signing does not fix this class**: a signed binary containing chisel is still flagged behaviourally; signing only addresses #185's generic false positive. The fix is to stop shipping the capability where it is unused: one interface per seam, `_linux.go` impl + `!linux` stub, commands still registered everywhere but failing with an actionable message. **Prove absence three ways, and know that `go tool nm` is not one of them** — release ldflags `-s -w` strip the symbol table, so nm reports "no symbols" whether or not the code is linked and would give a false pass; use `go list -deps`, `go version -m`, and `strings` (nm only on a deliberately unstripped build). **Guard against the transitive path, not the name**: match the module list (`x/crypto/ssh`, `gorilla/websocket`, `armon/go-socks5`, `jpillora/*`) so re-entry without the word "chisel" still trips it, and assert a **positive control** (chisel IS in the linux graph) so the check cannot pass vacuously. Verified by re-adding the import and watching the guard fail on all four windows/darwin targets. Result: -13.5 MB (-14.7%) on windows+darwin, linux unchanged. See ADR-0009 | +| CE7375 "must be published and be the key when associations are exposed as an associated object id" on a service publishing no associations | `PublishAssociations` is the representation, not a yes/no — `No` selects "as an associated object id", which needs the system ID as key | `mdl/executor/validate_odata_service_shape.go` | Set `PublishAssociations: Yes` ("as a link", and the default when omitted). MDL-ODATA06 warns at check time | +| `mx check` exits with `System.ArgumentOutOfRangeException: length ('-1')` and no error code | an OData service `Path` with no slash in it; mxbuild throws out of its own validator | `mdl/executor/validate_odata_service_shape.go` | Path must not start with `/` and must end with one (CE6550/CE6552). MDL-ODATA05 catches all three | diff --git a/.claude/skills/mendix/odata-data-sharing.md b/.claude/skills/mendix/odata-data-sharing.md index 627322a13..edd940401 100644 --- a/.claude/skills/mendix/odata-data-sharing.md +++ b/.claude/skills/mendix/odata-data-sharing.md @@ -471,8 +471,32 @@ Two things worth knowing before you write this: takes no parameters at all. `SkipSupported: No` and `TopSupported: No` turn off `$skip` and `$top` the same way. All three default to Yes. -`PublishAssociations` must stay at its default (Yes) here: a non-persistable -entity cannot publish its ID, so object-id mode can never build for it. +`PublishAssociations` must stay at its default (Yes) — and not only here. + +**It is not a yes/no, it is a two-value representation.** Studio Pro's own labels +for it are "As a link (recommended)" (Yes) and "As an associated object id" (No). +So `PublishAssociations: No` does not mean "this service publishes no +associations"; it selects the legacy representation, which requires the system +`ID` attribute published as the key. MDL cannot publish the system ID (CE1613), +so `No` cannot build from a script. + +That holds even when the service publishes no associations at all, and even for +a persistable entity with a perfectly good key of its own. Measured on Mendix +11.13, both arms of the same service: + +| | `mx check` | +|---|---| +| `PublishAssociations: No` | **CE7375** "Attribute ID … must be published and be the key when associations are exposed as an associated object id" | +| `PublishAssociations: Yes` | 0 errors | + +The error names a concept the script never mentions, which is why this costs +hours rather than minutes. `mxcli check` now warns (MDL-ODATA06). + +**`Path` has two rules and one trap.** No leading slash (CE6550), and it must end +with a single slash (CE6552). A path with **no slash at all** is the trap: mxbuild +throws `System.ArgumentOutOfRangeException` out of its own validator, with no +error code, no element name and no line, which reads as a corrupt project. Use +`'odata/thing/'`. `mxcli check` catches all three (MDL-ODATA05). ## HTTP Status Codes and Errors: What Each Capability Can Do diff --git a/cmd/mxcli/cmd_check.go b/cmd/mxcli/cmd_check.go index d42da4257..d311d9444 100644 --- a/cmd/mxcli/cmd_check.go +++ b/cmd/mxcli/cmd_check.go @@ -199,6 +199,13 @@ Examples: // Mendix refuses to build (CE0333). violations = append(violations, executor.ValidateODataAuth(prog)...) + // Flag two service shapes mxbuild rejects — a Path that breaks its + // slash rules, and the PublishAssociations mode whose name invites + // exactly the wrong value. A Path with no slash at all is the reason + // this is worth a check: mxbuild throws out of its own validator with + // no error code, so there is nothing to look up. + violations = append(violations, executor.ValidateODataServiceShape(prog)...) + // Flag a page whose widgets point at a page created further down the same // script. `exec` resolves page references in statement order and is not // transactional, so this fails after earlier statements are already diff --git a/mdl-examples/bug-tests/595-published-odata-entitytypepointer.mdl b/mdl-examples/bug-tests/595-published-odata-entitytypepointer.mdl index 0d83e71f2..e2805714f 100644 --- a/mdl-examples/bug-tests/595-published-odata-entitytypepointer.mdl +++ b/mdl-examples/bug-tests/595-published-odata-entitytypepointer.mdl @@ -21,7 +21,7 @@ create persistent entity bug595.Customer ( ); create odata service bug595.CustomerAPI ( - path: '/odata/customers', + path: 'odata/customers/', version: '1.0.0', ODataVersion: OData4, namespace: 'bug595.Customers' diff --git a/mdl-examples/bug-tests/ledger-113-odata-service-shape.mdl b/mdl-examples/bug-tests/ledger-113-odata-service-shape.mdl new file mode 100644 index 000000000..de6fbffa7 --- /dev/null +++ b/mdl-examples/bug-tests/ledger-113-odata-service-shape.mdl @@ -0,0 +1,55 @@ +-- ledger #113 — a published OData service failed to build with CE7375: +-- +-- [error] [CE7375] "Attribute ID for entity 'Ledger.VMonthCategory' must be +-- published and be the key when associations are exposed as an associated +-- object id." +-- +-- The reported cause was a missing mxcli property — an "association +-- representation" setting the service supposedly needs. There is no such +-- property: the word "Representation" appears nowhere in the Mendix Model SDK +-- (4.114.0, the latest published) and nowhere in Mendix 11.13's own metamodel +-- assemblies. +-- +-- `PublishAssociations` IS the representation. Studio Pro's two labels for it, +-- read out of Mendix.Modeler.Localization.dll, are "As a link (recommended)" +-- and "As an associated object id" — true and false. Measured on 11.13 against +-- a blank app, both arms, same service: +-- +-- PublishAssociations: No -> CE7375, publishing no associations at all +-- PublishAssociations: Yes -> 0 errors +-- +-- So the name invites exactly the wrong value: a service with no associations +-- reads `No` as obviously correct, and gets an error naming a concept the +-- script never mentions. MDL-ODATA06 says so at check time. +-- +-- The same probe found a second shape mxcli writes happily. mxbuild wants a +-- location with no leading slash that ends in a single slash; with NO slash at +-- all its own validator throws instead of reporting anything: +-- +-- Path 'cat' -> ERROR: System.ArgumentOutOfRangeException (no code) +-- Path '/cat/' -> CE6550 "The path should not start with a slash." +-- Path 'odata/cat' -> CE6552 "The location should end with a single slash." +-- Path 'odata/cat/' -> 0 errors +-- +-- MDL-ODATA05 catches all three before the build. +-- +-- mxcli check ledger-113-odata-service-shape.mdl # clean; this is the good shape +-- mx check .mpr # 0 errors + +create or replace entity MyFirstModule.Cat ( + Label: string(100) unique error 'must be unique' +); + +create or replace odata service MyFirstModule.Svc ( + Path: 'odata/cat/', + ServiceName: 'cat', + Namespace: 'cat', + Version: '1.0.0', + ODataVersion: V4, + PublishAssociations: Yes +) { + publish entity MyFirstModule.Cat expose ( + Label (KEY, Filterable, Sortable) + ) +} +/ diff --git a/mdl/executor/validate_odata_service_shape.go b/mdl/executor/validate_odata_service_shape.go new file mode 100644 index 000000000..aa6c2a6b7 --- /dev/null +++ b/mdl/executor/validate_odata_service_shape.go @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "fmt" + "strings" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/linter" +) + +// Two service-level shapes that MDL accepts, mxcli writes, and mxbuild then +// rejects — one of them without saying anything useful at all. +// +// Both were measured on Mendix 11.13 against a blank app, control and +// treatment, rather than read off the metamodel: +// +// Path 'cat' -> mxbuild CRASHES (ArgumentOutOfRangeException, no code) +// Path '/cat' -> CE6550 "The path should not start with a slash." +// Path 'odata/cat' -> CE6552 "The location should end with a single slash." +// Path 'cat/' -> 0 errors +// +// PublishAssociations: No -> CE7375, even with no associations published +// PublishAssociations: Yes -> 0 errors + +// ValidateODataServiceShape flags a published service that cannot build +// (MDL-ODATA05, MDL-ODATA06). +func ValidateODataServiceShape(prog *ast.Program) []linter.Violation { + if prog == nil { + return nil + } + var out []linter.Violation + for _, stmt := range prog.Statements { + switch s := stmt.(type) { + case *ast.CreateODataServiceStmt: + where := "odata service " + s.Name.String() + out = append(out, checkODataPath(where, s.Path)...) + // An unspecified value defaults to true (links) in the executor, + // which is the buildable one — so only an explicit No is worth a + // word. + if s.PublishAssociationsSet && !s.PublishAssociations { + out = append(out, associationModeViolation(where, len(s.Entities) > 0)) + } + case *ast.AlterODataServiceStmt: + where := "odata service " + s.Name.String() + if v, ok := s.Changes["Path"].(string); ok { + out = append(out, checkODataPath(where, v)...) + } + if v, ok := s.Changes["PublishAssociations"].(bool); ok && !v { + out = append(out, associationModeViolation(where, true)) + } + } + } + return out +} + +// checkODataPath applies mxbuild's two rules for a service location. +// +// The no-slash case is called out separately because it is the one where +// mxbuild does not report an error at all — it throws +// ArgumentOutOfRangeException out of its own validator, with no error code, no +// element name and no line, which reads as a corrupt project rather than as a +// one-character mistake. +func checkODataPath(where, path string) []linter.Violation { + if path == "" { + return nil + } + var out []linter.Violation + if strings.HasPrefix(path, "/") { + out = append(out, linter.Violation{ + RuleID: "MDL-ODATA05", + Severity: linter.SeverityError, + Message: fmt.Sprintf("%s: Path %q starts with a slash, which mxbuild rejects (CE6550)", where, path), + Suggestion: fmt.Sprintf("Use %q.", strings.TrimPrefix(path, "/")+"/"), + }) + return out + } + if !strings.HasSuffix(path, "/") { + v := linter.Violation{ + RuleID: "MDL-ODATA05", + Severity: linter.SeverityError, + Message: fmt.Sprintf("%s: Path %q does not end with a slash, which mxbuild rejects (CE6552)", where, path), + Suggestion: fmt.Sprintf("Use %q.", path+"/"), + } + if !strings.Contains(path, "/") { + v.Message = fmt.Sprintf("%s: Path %q contains no slash at all, which makes `mx check` "+ + "throw ArgumentOutOfRangeException instead of reporting an error", where, path) + v.Suggestion = fmt.Sprintf("Use %q. mxbuild wants a location ending in a single slash (CE6552); "+ + "with no slash its own validator crashes, so there is no error code to look up.", path+"/") + } + out = append(out, v) + } + return out +} + +// associationModeViolation explains the property whose name invites the wrong +// value. +// +// `PublishAssociations` is not "publish associations yes/no" — it is a two-value +// representation: true is "as a link (recommended)", false is "as an associated +// object id", which are Studio Pro's own labels for it. False then requires the +// system `ID` attribute published as the key, which MDL cannot express, so the +// build fails with CE7375 naming a concept the script never mentions. +// +// It stays a warning rather than an error because false is a legitimate Mendix +// mode for a service whose key is arranged elsewhere; what it is not is what it +// sounds like. +func associationModeViolation(where string, publishesEntities bool) linter.Violation { + v := linter.Violation{ + RuleID: "MDL-ODATA06", + Severity: linter.SeverityWarning, + Message: fmt.Sprintf("%s: PublishAssociations: No does not mean \"publish no associations\" — "+ + "it selects the \"as an associated object id\" representation", where), + Suggestion: "That representation requires the system ID attribute published as the key, " + + "which MDL cannot express, so mxbuild fails with CE7375 even when the service publishes " + + "no associations at all. Use Yes for \"as a link (recommended)\", which is also the " + + "default when the property is omitted.", + } + if !publishesEntities { + v.Severity = linter.SeverityInfo + } + return v +} diff --git a/mdl/executor/validate_odata_service_shape_test.go b/mdl/executor/validate_odata_service_shape_test.go new file mode 100644 index 000000000..20c442d57 --- /dev/null +++ b/mdl/executor/validate_odata_service_shape_test.go @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Ledger finding #113: a published OData service failed to build with CE7375 +// and the reported cause was a missing mxcli property. It is not — the property +// exists, and the value the script gave it is the one that cannot build. +// +// Measured on Mendix 11.13 against a blank app, both arms: +// +// PublishAssociations: No -> CE7375, even publishing no associations at all +// PublishAssociations: Yes -> 0 errors +// +// The same probe turned up a second shape mxcli writes happily: a Path with no +// slash makes mxbuild throw out of its own validator, with no error code and no +// element name, which reads as a corrupt project rather than a typo. +package executor + +import ( + "strings" + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/linter" +) + +func serviceStmt(path string, assoc, assocSet bool) *ast.Program { + return &ast.Program{Statements: []ast.Statement{ + &ast.CreateODataServiceStmt{ + Name: ast.QualifiedName{Module: "M", Name: "Svc"}, + Path: path, + PublishAssociations: assoc, + PublishAssociationsSet: assocSet, + Entities: []*ast.PublishedEntityDef{{}}, + }, + }} +} + +func shapeRuleIDs(vs []linter.Violation) string { + var ids []string + for _, v := range vs { + ids = append(ids, v.RuleID) + } + return strings.Join(ids, ",") +} + +// TestODataPathSlashRules pins mxbuild's two rules, and the third case that is +// the reason for the check: no slash at all is not an error message, it is a +// .NET stack trace. +func TestODataPathSlashRules(t *testing.T) { + cases := []struct { + path string + wantRule bool + wantPhras string + }{ + {"odata/cat/", false, ""}, // the buildable shape + {"cat/", false, ""}, // a single trailing slash is enough + {"/cat/", true, "starts with a slash"}, // CE6550 + {"odata/cat", true, "does not end with a slash"}, // CE6552 + {"cat", true, "contains no slash at all"}, // mxbuild crashes + {"", false, ""}, // unset — not this check's business + } + for _, tc := range cases { + got := ValidateODataServiceShape(serviceStmt(tc.path, true, true)) + var pathViolations []linter.Violation + for _, v := range got { + if v.RuleID == "MDL-ODATA05" { + pathViolations = append(pathViolations, v) + } + } + if tc.wantRule && len(pathViolations) == 0 { + t.Errorf("Path %q: no MDL-ODATA05, want one", tc.path) + continue + } + if !tc.wantRule && len(pathViolations) > 0 { + t.Errorf("Path %q: unexpected %s", tc.path, shapeRuleIDs(pathViolations)) + continue + } + if tc.wantRule && !strings.Contains(pathViolations[0].Message, tc.wantPhras) { + t.Errorf("Path %q: message %q, want it to mention %q", + tc.path, pathViolations[0].Message, tc.wantPhras) + } + } +} + +// TestPublishAssociationsNoIsExplained is the ledger's case. The suggestion has +// to carry the error code, because CE7375 names "associated object id" — a +// phrase that appears nowhere in the script that caused it. +func TestPublishAssociationsNoIsExplained(t *testing.T) { + got := ValidateODataServiceShape(serviceStmt("odata/cat/", false, true)) + if len(got) != 1 || got[0].RuleID != "MDL-ODATA06" { + t.Fatalf("got %v, want one MDL-ODATA06", shapeRuleIDs(got)) + } + if got[0].Severity != linter.SeverityWarning { + t.Errorf("severity = %v, want warning: false is a legitimate Mendix mode, just not the one it sounds like", got[0].Severity) + } + for _, want := range []string{"CE7375", "Yes"} { + if !strings.Contains(got[0].Suggestion, want) { + t.Errorf("suggestion does not mention %q: %s", want, got[0].Suggestion) + } + } +} + +// TestPublishAssociationsYesAndOmittedAreQuiet — the executor defaults an +// unspecified value to true, which is the buildable one, so neither the default +// nor an explicit Yes has anything to warn about. A rule that fired on the +// correct spelling would be noise on every service in a project. +func TestPublishAssociationsYesAndOmittedAreQuiet(t *testing.T) { + if got := ValidateODataServiceShape(serviceStmt("odata/cat/", true, true)); len(got) > 0 { + t.Errorf("explicit Yes produced %s", shapeRuleIDs(got)) + } + if got := ValidateODataServiceShape(serviceStmt("odata/cat/", false, false)); len(got) > 0 { + t.Errorf("omitted (defaults to Yes) produced %s", shapeRuleIDs(got)) + } +} + +// TestAlterIsCheckedToo — the property is settable both ways, and a script that +// creates a good service then alters it to No has the same build failure. +func TestAlterODataServiceShape(t *testing.T) { + prog := &ast.Program{Statements: []ast.Statement{ + &ast.AlterODataServiceStmt{ + Name: ast.QualifiedName{Module: "M", Name: "Svc"}, + Changes: map[string]any{"PublishAssociations": false, "Path": "cat"}, + }, + }} + got := ValidateODataServiceShape(prog) + if !strings.Contains(shapeRuleIDs(got), "MDL-ODATA05") || !strings.Contains(shapeRuleIDs(got), "MDL-ODATA06") { + t.Errorf("alter produced %s, want both rules", shapeRuleIDs(got)) + } +} From e34f99d28969afbf018bbd251469e703716f0c09 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 21:04:24 +0000 Subject: [PATCH 13/22] Say why links are the right answer, not just that they build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The check told people to set PublishAssociations: Yes and stopped there, which leaves the other reading of CE7375 open: publish the ID and make it the key. That is the worse move, and the error message recommends it. The two representations are historical. OData v3 had no link support, so a foreign key had to be an exposed object id; v4 added links largely so internal ids no longer had to leave the app. Choosing object-id mode now gives that back up. And a Mendix object id is not a key you want in a contract: it is autogenerated and not stable across an app landscape, so the same record carries different ids in test, acceptance and production. An id baked into an external contract breaks the moment a consumer moves between environments or compares data from two of them. A published key should be a business value the domain already guarantees — an invoice number, an ISIN, an employee number. Mendix requires a key to be unique, required and stable, and the unique validation rule it makes you add (CE6624) is checking exactly that. Measured while confirming the rule did not need version-scoping: on 11.13 `PublishAssociations: Yes` builds under both OData3 and OData4, so links are not a v4-only option in current Mendix and MDL-ODATA06 is correct for both. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4 --- .claude/skills/mendix/odata-data-sharing.md | 22 ++++++++++++++++++++ mdl/executor/validate_odata_service_shape.go | 12 ++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/.claude/skills/mendix/odata-data-sharing.md b/.claude/skills/mendix/odata-data-sharing.md index edd940401..19a9f2a92 100644 --- a/.claude/skills/mendix/odata-data-sharing.md +++ b/.claude/skills/mendix/odata-data-sharing.md @@ -492,6 +492,28 @@ a persistable entity with a perfectly good key of its own. Measured on Mendix The error names a concept the script never mentions, which is why this costs hours rather than minutes. `mxcli check` now warns (MDL-ODATA06). +**Do not take CE7375's advice literally.** It says to publish the `ID` and make +it the key, and that is the wrong direction for anything you share outside the +app. The two representations exist for a reason: OData v3 had no link support, +so a foreign key had to be an exposed object id; v4 added links largely so +internal ids no longer had to leave the app. Going back to ids gives up that. + +**A published key should be a business key.** Mendix object ids are +autogenerated and are not stable across an app landscape — the same record has +different ids in test, acceptance and production — so an id baked into an +external contract breaks the moment a consumer moves between environments, or +compares data from two of them. Pick something the business already guarantees: +an invoice number, an ISIN, an employee number. Mendix requires a key to be +unique, required and stable (the last is the point here), and the unique +validation rule it makes you add is checking exactly that. + +That is also why the key needs `unique error '…'` on the attribute — see the +CE6624 note below. Both halves of the same idea: the value identifies one row, +and keeps identifying it. + +(Measured on 11.13: `PublishAssociations: Yes` builds under both `OData3` and +`OData4`, so choosing links is not a v4-only option in current Mendix.) + **`Path` has two rules and one trap.** No leading slash (CE6550), and it must end with a single slash (CE6552). A path with **no slash at all** is the trap: mxbuild throws `System.ArgumentOutOfRangeException` out of its own validator, with no diff --git a/mdl/executor/validate_odata_service_shape.go b/mdl/executor/validate_odata_service_shape.go index aa6c2a6b7..5d0d9592d 100644 --- a/mdl/executor/validate_odata_service_shape.go +++ b/mdl/executor/validate_odata_service_shape.go @@ -103,6 +103,13 @@ func checkODataPath(where, path string) []linter.Violation { // system `ID` attribute published as the key, which MDL cannot express, so the // build fails with CE7375 naming a concept the script never mentions. // +// The two modes are historical: OData v3 had no link support, so a foreign key +// had to be an exposed object id; v4 added links largely so internal ids no +// longer had to leave the app. Which is why CE7375's own advice — publish the +// ID and make it the key — is the wrong direction for a shared contract: object +// ids are autogenerated and differ per environment, so one baked into an +// external contract breaks the moment a consumer moves between them. +// // It stays a warning rather than an error because false is a legitimate Mendix // mode for a service whose key is arranged elsewhere; what it is not is what it // sounds like. @@ -115,7 +122,10 @@ func associationModeViolation(where string, publishesEntities bool) linter.Viola Suggestion: "That representation requires the system ID attribute published as the key, " + "which MDL cannot express, so mxbuild fails with CE7375 even when the service publishes " + "no associations at all. Use Yes for \"as a link (recommended)\", which is also the " + - "default when the property is omitted.", + "default when the property is omitted. Do not follow CE7375 literally and publish the " + + "ID instead: object ids are autogenerated and differ between test, acceptance and " + + "production, so an id in an external contract breaks when a consumer changes " + + "environment. Key on a business value.", } if !publishesEntities { v.Severity = linter.SeverityInfo From da189cfc48be20d0f7f497a933834155262a04c6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 21:11:24 +0000 Subject: [PATCH 14/22] Say what to key an aggregate resource on, since a grain is not a business key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The business-key advice has a hole exactly where the ledger is standing: a summary resource has no business key to reach for. Monthly totals per category are not an invoice, and nothing in the domain issues them a number. The key is the grain — the columns the aggregate groups by. They identify one row, they are stable because they ARE the definition of the row, and they mean the same thing in every environment. What the ledger reached for instead, `cast(c.id as string) as RowId`, satisfies "a key" while reintroducing the id problem one level down: autogenerated, environment-specific, and stable only until the view is rebuilt. Measured on 11.13, each row a separate build: single key, persistable, no unique rule -> CE6624 single key, persistable, unique error '…' -> 0 errors composite key, OData3 -> CE7238 (v4 only) composite key, OData4, persistable -> 0 errors composite key, OData4, NON-persistable -> 0 errors any validation rule on non-persistable -> CE0070 Two consequences that are not obvious from either error: - A grain key requires ODataVersion: OData4. More than one key attribute is a v4 feature; the same model on v3 is CE7238. - A composite key needs no unique validation rule, and a non-persistable entity could not carry one anyway (CE0070). CE6624 only applies to a SINGLE-attribute key, where one attribute must be unique by itself — precisely what a grain is not. So the hurdle the ledger hit disappears once the key stops pretending to be one column. ledger-113b-odata-grain-key.mdl is the shape, and it is not just parsed: applied to a blank 11.13 app it is 0 errors, non-persistable entity and composite grain key included. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4 --- .claude/skills/mendix/odata-data-sharing.md | 56 +++++++++++++++++ .../bug-tests/ledger-113b-odata-grain-key.mdl | 62 +++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 mdl-examples/bug-tests/ledger-113b-odata-grain-key.mdl diff --git a/.claude/skills/mendix/odata-data-sharing.md b/.claude/skills/mendix/odata-data-sharing.md index 19a9f2a92..40fc882b3 100644 --- a/.claude/skills/mendix/odata-data-sharing.md +++ b/.claude/skills/mendix/odata-data-sharing.md @@ -511,6 +511,62 @@ That is also why the key needs `unique error '…'` on the attribute — see the CE6624 note below. Both halves of the same idea: the value identifies one row, and keeps identifying it. +### An aggregate view's key is its grain + +A summary resource — an OQL view entity, or a non-persistable row filled by a +read microflow — has no business key to reach for. Monthly totals per category +are not an invoice; nothing in the domain issues them a number. + +**The key is the grain: the columns the aggregate groups by.** For monthly +totals per category that is `(Period, Category)` — together they identify +exactly one row, they are stable because they are the definition of the row, and +they mean the same thing in every environment. + +What not to do is cast the internal id into a column (`cast(c.id as string) as +RowId`) and publish that. It satisfies "a key" and it is the id problem again, +one level down: autogenerated, environment-specific, and now stable only as long +as nobody rebuilds the view. + +Measured on Mendix 11.13, each row a separate build: + +| shape | result | +|---|---| +| single key attribute, persistable, no `unique` rule | **CE6624** — add one | +| single key attribute, persistable, `unique error '…'` | 0 errors | +| **composite key, `OData3`** | **CE7238** "You can only have more than one key attribute when the OData version is 4" | +| composite key, `OData4`, persistable, no `unique` rules | 0 errors | +| **composite key, `OData4`, non-persistable, no `unique` rules** | **0 errors** | +| any validation rule on a non-persistable entity | **CE0070** — not allowed | + +Two consequences worth holding on to: + +- **A grain key needs `ODataVersion: OData4`.** More than one key attribute is a + v4 feature; on v3 the same model is CE7238. +- **A composite key needs no `unique` validation rule**, and a non-persistable + entity could not carry one anyway (CE0070). The rule is only demanded for a + *single*-attribute key, where one attribute has to be unique by itself — + which is exactly the case a grain is not. So the CE6624 hurdle disappears + the moment the key is honest about being multi-column. + +```sql +create non-persistent entity Fin.VMonthCategory ( + Period: string(7), -- 2026-08 + Category: string(60), + Total: decimal +); + +create odata service Fin.ChartApi ( + path: 'odata/charts/', ServiceName: 'ChartApi', Namespace: 'Fin.Charts', + version: '1.0.0', ODataVersion: OData4 -- required: the key is composite +) { + publish entity Fin.VMonthCategory ( + ReadMode: microflow Fin.Read_MonthCategory, + Countable: No -- else CE6962 wants System.ODataResponse + ) + expose ( Period (KEY), Category (KEY), Total ) +} +``` + (Measured on 11.13: `PublishAssociations: Yes` builds under both `OData3` and `OData4`, so choosing links is not a v4-only option in current Mendix.) diff --git a/mdl-examples/bug-tests/ledger-113b-odata-grain-key.mdl b/mdl-examples/bug-tests/ledger-113b-odata-grain-key.mdl new file mode 100644 index 000000000..366aaab6a --- /dev/null +++ b/mdl-examples/bug-tests/ledger-113b-odata-grain-key.mdl @@ -0,0 +1,62 @@ +-- ledger #113, the follow-on: what to key an aggregate resource on. +-- +-- A summary resource — an OQL view entity, or a non-persistable row filled by a +-- read microflow — has no business key to reach for. Monthly totals per +-- category are not an invoice; nothing in the domain issues them a number. +-- +-- The key is the GRAIN: the columns the aggregate groups by. They identify one +-- row, they are stable because they are the definition of the row, and they mean +-- the same thing in every environment. Casting the internal id into a column +-- (`cast(c.id as string) as RowId`) satisfies "a key" and reintroduces the id +-- problem one level down — autogenerated, environment-specific, and stable only +-- until the view is rebuilt. +-- +-- Measured on Mendix 11.13, each row a separate build: +-- +-- single key, persistable, no unique rule -> CE6624 +-- single key, persistable, unique error '…' -> 0 errors +-- composite key, OData3 -> CE7238 (v4 only) +-- composite key, OData4, persistable -> 0 errors +-- composite key, OData4, NON-persistable -> 0 errors <- this file +-- any validation rule on a non-persistable -> CE0070 +-- +-- Two consequences: a grain key requires ODataVersion: OData4, and it needs no +-- unique validation rule — which a non-persistable entity could not carry anyway. +-- The CE6624 hurdle only applies to a single-attribute key, which is exactly what +-- a grain is not. +-- +-- mxcli check ledger-113b-odata-grain-key.mdl +-- mx check .mpr # 0 errors + +create or replace non-persistent entity MyFirstModule.VMonthCategory ( + Period: string(7), + Category: string(60), + Total: decimal +); + +create or replace microflow MyFirstModule.Read_MonthCategory () + returns List of MyFirstModule.VMonthCategory as $Rows +begin + $Rows = create list of MyFirstModule.VMonthCategory; + return $Rows; +end; + +create or replace odata service MyFirstModule.ChartApi ( + Path: 'odata/charts/', + ServiceName: 'ChartApi', + Namespace: 'MyFirstModule.Charts', + Version: '1.0.0', + ODataVersion: OData4, + PublishAssociations: Yes +) { + publish entity MyFirstModule.VMonthCategory ( + ReadMode: microflow MyFirstModule.Read_MonthCategory, + Countable: No + ) + expose ( + Period (KEY, Filterable, Sortable), + Category (KEY, Filterable, Sortable), + Total (Filterable, Sortable) + ) +} +/ From 046e1c406f4551cf3fe85a175fc1bb00ba7d4884 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 21:29:50 +0000 Subject: [PATCH 15/22] Let MDL publish a service as GraphQL as well as OData MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A published OData service can answer GraphQL too — one boolean, the same resources. Every layer already had it except the one an author reaches: generated/metamodel binds it, modelsdk/gen has SupportsGraphQL() and its setter, and both engines' writers take arbitrary bools. Only knownODataServiceProps did not list it, and unknown OData properties are an ERROR (MDL-ODATA01), so a script asking for GraphQL was rejected outright. Wired through the whole pipeline: model.PublishedODataService, the AST (with Set, so `create or modify` that does not mention it cannot turn GraphQL off on a service that has it), the visitor, create/alter/describe, both writers, both readers, the known-property list, syntax help and the quick reference. No default is inferred. Unlike PublishAssociations, where false can never build, false is simply what every service was before the property existed — so an omitted value is left alone rather than opted in. Gated at Mendix 10.14, where the release notes introduce it as experimental: "Studio Pro now supports publishing GraphQL services." The gate is a real guard rather than a nicety, because writing a property a version's metamodel does not have is not a build error — it is a document Studio Pro refuses to open. The floor is documentary, not measured: the Mendix CDN serves no 10.x mxbuild from here, so the earliest assembly I could inspect is 11.x. Recorded as such in the registry. Verified against a RUNNING 11.13 app, not just the build, because "the model stores a flag" and "the app answers GraphQL" are different claims: GET /odata/charts/$metadata -> 200, OData unchanged POST /odata/charts/ {__schema{queryType{name}}} -> {"data":{"__schema":{"queryType":{"name":"Query"}}}} POST /odata/charts/ {monthCategories{period category total}} -> {"data":{"monthCategories":[]}} The endpoint is the service LOCATION — there is no /graphql path, which cost a while to establish: /graphql, /graphql/ and /graphql are all 404. Two constraints that appear only once GraphQL is on, both found by building rather than by reading: - Exposed names must be unique beyond case (CE2881). Publishing an entity without `as '...'` gives the type and the set the same name, which OData accepts and GraphQL does not — so a service that built yesterday fails the day it is enabled. - Query field names are camelCased. `Period` is `period`, and asking for `Total` is a 400 "Field 'Total' not found". The two surfaces spell the same attribute differently. DESCRIBE emits it only when on, since false is every pre-existing service and printing it on each would be noise in every description. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4 --- .claude/skills/mendix/odata-data-sharing.md | 47 +++++++++ cmd/mxcli/syntax/features_integration.go | 9 +- docs/01-project/MDL_QUICK_REFERENCE.md | 1 + .../bug-tests/odata-graphql-service.mdl | 66 +++++++++++++ mdl/ast/ast_odata.go | 9 +- mdl/backend/modelsdk/integration_read.go | 1 + mdl/backend/modelsdk/odata_write.go | 1 + mdl/executor/cmd_odata.go | 24 +++++ mdl/executor/cmd_odata_graphql_test.go | 98 +++++++++++++++++++ mdl/executor/validate_odata_properties.go | 2 +- mdl/visitor/visitor_odata.go | 3 + model/types.go | 25 ++--- sdk/mpr/parser_odata.go | 1 + sdk/mpr/writer_odata.go | 1 + sdk/versions/mendix-10.yaml | 15 +++ sdk/versions/mendix-11.yaml | 15 +++ 16 files changed, 304 insertions(+), 14 deletions(-) create mode 100644 mdl-examples/bug-tests/odata-graphql-service.mdl create mode 100644 mdl/executor/cmd_odata_graphql_test.go diff --git a/.claude/skills/mendix/odata-data-sharing.md b/.claude/skills/mendix/odata-data-sharing.md index 40fc882b3..e70e31c36 100644 --- a/.claude/skills/mendix/odata-data-sharing.md +++ b/.claude/skills/mendix/odata-data-sharing.md @@ -576,6 +576,53 @@ throws `System.ArgumentOutOfRangeException` out of its own validator, with no error code, no element name and no line, which reads as a corrupt project. Use `'odata/thing/'`. `mxcli check` catches all three (MDL-ODATA05). +## Also Publishing as GraphQL + +`SupportsGraphQL: Yes` makes the same service answer GraphQL as well as OData. +One boolean; the OData surface is untouched. + +```sql +create odata service Fin.ChartApi ( + path: 'odata/charts/', ServiceName: 'ChartApi', Namespace: 'Fin.Charts', + version: '1.0.0', ODataVersion: OData4, + SupportsGraphQL: Yes +) { ... } +``` + +**The GraphQL endpoint is the service location itself** — there is no `/graphql` +path. Clients `POST` a query to the same URL that serves OData: + +``` +GET /odata/charts/$metadata -> the OData contract +POST /odata/charts/ -> {"query":"{ monthCategories { period } }"} +``` + +Verified against a running Mendix 11.13 app: + +| request | response | +|---|---| +| `POST` `{ __schema { queryType { name } } }` | `{"data":{"__schema":{"queryType":{"name":"Query"}}}}` | +| `POST` `{ monthCategories { period category total } }` | `{"data":{"monthCategories":[]}}` | + +Three things that only bite once GraphQL is on: + +- **Query field names are camelCased.** `Period` in the model is `period` in a + query; asking for `Total` returns 400 + `{"errors":[{"message":"Field 'Total' not found"}]}`. The OData names are + unchanged, so the two surfaces spell the same attribute differently. +- **Exposed names must be unique beyond case (CE2881).** Publishing an entity + without `as '...'` gives the entity type and the entity set the same name, + which OData accepts and GraphQL rejects. A service that built yesterday can + fail on the day it is enabled. Give the set its own name: + `publish entity Fin.VMonthCategory as 'MonthCategories'`. +- **Mendix 10.14+**, where it arrived as an experimental feature. mxcli refuses + the statement on an older project rather than writing a property that version's + metamodel does not have — an unknown property is not a build error, it is a + document Studio Pro will not open. + +GraphQL here is not as complete as the OData surface — it is a second way to read +the same published resources, which some widgets and clients prefer. + ## HTTP Status Codes and Errors: What Each Capability Can Do **The read path and the write path have different powers, and the difference is diff --git a/cmd/mxcli/syntax/features_integration.go b/cmd/mxcli/syntax/features_integration.go index 45149caba..7f09f44d9 100644 --- a/cmd/mxcli/syntax/features_integration.go +++ b/cmd/mxcli/syntax/features_integration.go @@ -74,6 +74,8 @@ func init() { "authentication", "page size", "servicename", "publishassociations", "readmode microflow", "non-persistable", "countable", "skipsupported", "topsupported", + "graphql", + "supportsgraphql", }, Syntax: "CREATE [OR MODIFY] ODATA SERVICE Module.Name (\n" + " path: 'odata/customers/', -- no leading slash; trailing slash required\n" + @@ -81,7 +83,12 @@ func init() { " ODataVersion: OData4,\n" + " namespace: 'Module.Customers',\n" + " ServiceName: 'CustomerApi', -- optional; defaults to the document name\n" + - " PublishAssociations: Yes -- optional; default Yes (associations as links)\n" + + " PublishAssociations: Yes, -- optional; default Yes (associations as links)\n" + + " SupportsGraphQL: Yes -- optional; also answer GraphQL at the SAME\n" + + " -- location (POST a query). Mendix 10.14+.\n" + + " -- Exposed names must then be unique beyond\n" + + " -- case (CE2881), and query fields are\n" + + " -- camelCased: Period -> period\n" + ")\n" + "authentication basic, session\n" + "-- or, for custom authentication (no per-request password hash):\n" + diff --git a/docs/01-project/MDL_QUICK_REFERENCE.md b/docs/01-project/MDL_QUICK_REFERENCE.md index a8ba876ea..fc8ffd027 100644 --- a/docs/01-project/MDL_QUICK_REFERENCE.md +++ b/docs/01-project/MDL_QUICK_REFERENCE.md @@ -305,6 +305,7 @@ create scheduled event Ops.WeeklyReport ( | Show OData services | `show odata services [in module];` | Published OData services | | Describe OData service | `describe odata service Module.Name;` | Full MDL output | | Create OData service | `create [or modify] odata service Module.Name (...) authentication ... { publish entity ... };` | | +| Publish as GraphQL too | `create odata service Module.Name (SupportsGraphQL: Yes) {...};` | Mendix 10.14+. Same location, clients POST a query. Exposed names must be unique beyond case (CE2881); query fields are camelCased | | Alter OData service | `alter odata service Module.Name set key = value;` | | | Drop OData service | `drop odata service Module.Name;` | | | Show external entities | `show external entities [in module];` | OData-backed entities | diff --git a/mdl-examples/bug-tests/odata-graphql-service.mdl b/mdl-examples/bug-tests/odata-graphql-service.mdl new file mode 100644 index 000000000..804ccba7f --- /dev/null +++ b/mdl-examples/bug-tests/odata-graphql-service.mdl @@ -0,0 +1,66 @@ +-- Publishing the same resources over GraphQL as well as OData. +-- +-- One boolean: `SupportsGraphQL: Yes`. The OData surface is unchanged, and the +-- GraphQL endpoint is the SAME location — clients POST a query to it rather +-- than GET a resource path. Introduced in Studio Pro 10.14 (experimental), so +-- mxcli gates it: on an older project the CREATE is refused rather than writing +-- a property that version's metamodel does not have, which is what makes Studio +-- Pro refuse to open a document. +-- +-- Verified against a RUNNING Mendix 11.13 app (`mxcli run --local`), not just +-- against the build: +-- +-- GET /odata/charts/$metadata -> 200 (OData intact) +-- POST /odata/charts/ {"query":"{ __schema { queryType { name } } }"} +-- -> {"data": {"__schema": {"queryType": {"name": "Query"}}}} +-- POST /odata/charts/ {"query":"{ monthCategories { period category total } }"} +-- -> {"data":{"monthCategories":[]}} +-- +-- (The list is empty because the read microflow below returns an empty list.) +-- +-- Two things the OData path never makes you think about: +-- +-- 1. GraphQL field names are CAMELCASED. `Period` is `period` in a query, and +-- asking for `Total` is a 400: {"errors":[{"message":"Field 'Total' not found"}]} +-- 2. Exposed names must be unique beyond case (CE2881). Publishing an entity +-- without `as '...'` gives the type and the set the same name, which OData +-- accepts and GraphQL does not: +-- [error] [CE2881] "Exposed name 'VMonthCategory' occurs more than once in +-- this service. GraphQL services must use unique exposed names that differ +-- by more than just case." +-- Hence `as 'MonthCategories'` below. The rule only appears once GraphQL is +-- on, so a service that built yesterday can fail on the day it is enabled. + +create or replace non-persistent entity MyFirstModule.VMonthCategory ( + Period: string(7), + Category: string(60), + Total: decimal +); + +create or replace microflow MyFirstModule.Read_MonthCategory () + returns List of MyFirstModule.VMonthCategory as $Rows +begin + $Rows = create list of MyFirstModule.VMonthCategory; + return $Rows; +end; + +create or replace odata service MyFirstModule.ChartApi ( + Path: 'odata/charts/', + ServiceName: 'ChartApi', + Namespace: 'MyFirstModule.Charts', + Version: '1.0.0', + ODataVersion: OData4, + PublishAssociations: Yes, + SupportsGraphQL: Yes +) { + publish entity MyFirstModule.VMonthCategory as 'MonthCategories' ( + ReadMode: microflow MyFirstModule.Read_MonthCategory, + Countable: No + ) + expose ( + Period (KEY, Filterable, Sortable), + Category (KEY, Filterable, Sortable), + Total (Filterable, Sortable) + ) +} +/ diff --git a/mdl/ast/ast_odata.go b/mdl/ast/ast_odata.go index a7c9aa524..5d97b9cd5 100644 --- a/mdl/ast/ast_odata.go +++ b/mdl/ast/ast_odata.go @@ -101,7 +101,14 @@ type CreateODataServiceStmt struct { // author's choice and is written as given. PublishAssociations bool PublishAssociationsSet bool - AuthenticationTypes []string + // SupportsGraphQL publishes the same resources over GraphQL too. Unlike + // PublishAssociations there is no useful default to infer: false is what + // every service was before, so an unset value is left alone rather than + // opted in. Set records whether the author said anything, which is what + // keeps `alter` from turning it off on a service that had it on. + SupportsGraphQL bool + SupportsGraphQLSet bool + AuthenticationTypes []string // AuthMicroflow is the microflow named by `authentication microflow X`. // Custom authentication is the only method that carries a target, and // Mendix rejects the service without one (CE0333 "Please select a microflow diff --git a/mdl/backend/modelsdk/integration_read.go b/mdl/backend/modelsdk/integration_read.go index 49bdcd3b7..262947733 100644 --- a/mdl/backend/modelsdk/integration_read.go +++ b/mdl/backend/modelsdk/integration_read.go @@ -110,6 +110,7 @@ func (b *Backend) ListPublishedODataServices() ([]*model.PublishedODataService, Summary: g.Summary(), Description: g.Description(), PublishAssociations: g.PublishAssociations(), + SupportsGraphQL: g.SupportsGraphQL(), UseGeneralization: g.UseGeneralization(), Excluded: g.Excluded(), AuthMicroflow: g.AuthenticationMicroflowQualifiedName(), diff --git a/mdl/backend/modelsdk/odata_write.go b/mdl/backend/modelsdk/odata_write.go index 7f803c856..6c68458b9 100644 --- a/mdl/backend/modelsdk/odata_write.go +++ b/mdl/backend/modelsdk/odata_write.go @@ -239,6 +239,7 @@ func publishedODataServiceToGen(svc *model.PublishedODataService) element.Elemen addStr(g, "Summary", svc.Summary) addStr(g, "Description", svc.Description) addBool(g, "PublishAssociations", svc.PublishAssociations) + addBool(g, "SupportsGraphQL", svc.SupportsGraphQL) addBool(g, "UseGeneralization", svc.UseGeneralization) addStr(g, "AuthenticationMicroflow", svc.AuthMicroflow) // AllowedModuleRoles is written unconditionally, marker 1, matching diff --git a/mdl/executor/cmd_odata.go b/mdl/executor/cmd_odata.go index c30f05068..bbbb91b16 100644 --- a/mdl/executor/cmd_odata.go +++ b/mdl/executor/cmd_odata.go @@ -344,6 +344,11 @@ func outputPublishedODataServiceMDL(ctx *ExecContext, svc *model.PublishedODataS if svc.PublishAssociations { props = append(props, " PublishAssociations: Yes") } + // Only emitted when on: false is what every service was before the property + // existed, so printing it on each of them would be noise in every describe. + if svc.SupportsGraphQL { + props = append(props, " SupportsGraphQL: Yes") + } fmt.Fprintln(ctx.Output, strings.Join(props, ",\n")) fmt.Fprintln(ctx.Output, ")") @@ -1335,6 +1340,19 @@ func createODataService(ctx *ExecContext, stmt *ast.CreateODataServiceStmt) erro return mdlerrors.NewValidation("module name required: use create odata service Module.Name (...)") } + // Gate before the write, not after: a property the project's Mendix version + // does not have is not a build error, it is a document Studio Pro refuses to + // open (InvalidOperationException at MprProperty.cs). Only checked when the + // author asked for it, so nothing changes for the services that do not. + if stmt.SupportsGraphQL { + if err := checkFeature(ctx, "integration", "odata_graphql", + "SupportsGraphQL on a published OData service", + "Publishing a service as GraphQL as well arrived in Studio Pro 10.14. "+ + "Remove SupportsGraphQL to publish OData only."); err != nil { + return err + } + } + module, err := findModule(ctx, stmt.Name.Module) if err != nil { return err @@ -1380,6 +1398,9 @@ func createODataService(ctx *ExecContext, stmt *ast.CreateODataServiceStmt) erro if stmt.PublishAssociationsSet { svc.PublishAssociations = stmt.PublishAssociations } + if stmt.SupportsGraphQLSet { + svc.SupportsGraphQL = stmt.SupportsGraphQL + } if len(stmt.Microflows) > 0 { published, mfErr := astMicroflowDefsToModel(ctx, stmt.Microflows) if mfErr != nil { @@ -1473,6 +1494,7 @@ func createODataService(ctx *ExecContext, stmt *ast.CreateODataServiceStmt) erro Summary: stmt.Summary, Description: stmt.Description, PublishAssociations: publishAssociationsFor(stmt), + SupportsGraphQL: stmt.SupportsGraphQL, AuthenticationTypes: stmt.AuthenticationTypes, AuthMicroflow: stmt.AuthMicroflow, } @@ -1555,6 +1577,8 @@ func alterODataService(ctx *ExecContext, stmt *ast.AlterODataServiceStmt) error svc.Description = strVal case "publishassociations": svc.PublishAssociations = strings.EqualFold(strVal, "true") || strings.EqualFold(strVal, "yes") + case "supportsgraphql": + svc.SupportsGraphQL = strings.EqualFold(strVal, "true") || strings.EqualFold(strVal, "yes") default: return mdlerrors.NewUnsupported(fmt.Sprintf("unknown OData service property: %s", key)) } diff --git a/mdl/executor/cmd_odata_graphql_test.go b/mdl/executor/cmd_odata_graphql_test.go new file mode 100644 index 000000000..9ec916b9b --- /dev/null +++ b/mdl/executor/cmd_odata_graphql_test.go @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: Apache-2.0 + +// A published OData service can answer GraphQL as well — one boolean, the same +// resources, and the SAME endpoint (clients POST a query to the service +// location rather than GET a resource path). +// +// Verified against a running Mendix 11.13 app rather than only against the +// build, because "the model stores a flag" and "the app answers GraphQL" are +// different claims: +// +// POST /odata/charts/ { __schema { queryType { name } } } +// -> {"data": {"__schema": {"queryType": {"name": "Query"}}}} +// POST /odata/charts/ { monthCategories { period category total } } +// -> {"data":{"monthCategories":[]}} +package executor + +import ( + "strings" + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/visitor" +) + +func parseService(t *testing.T, src string) *ast.CreateODataServiceStmt { + t.Helper() + prog, errs := visitor.Build(src) + if len(errs) > 0 { + t.Fatalf("parse errors: %v", errs) + } + stmt, ok := prog.Statements[0].(*ast.CreateODataServiceStmt) + if !ok { + t.Fatalf("statement is %T, want CreateODataServiceStmt", prog.Statements[0]) + } + return stmt +} + +const gqlService = `create odata service M.Api ( + Path: 'odata/charts/', ServiceName: 'Api', Namespace: 'M.Charts', + Version: '1.0.0', ODataVersion: OData4, SupportsGraphQL: %s +) { publish entity M.Row as 'Rows' expose ( K (KEY) ) } +/` + +// TestSupportsGraphQLParses — the property used to be rejected outright by +// MDL-ODATA01 as unknown, which is the whole reason a project could not enable +// GraphQL from a script. +func TestSupportsGraphQLParses(t *testing.T) { + for _, tc := range []struct { + literal string + want bool + }{ + {"Yes", true}, {"true", true}, {"No", false}, {"false", false}, + } { + stmt := parseService(t, strings.Replace(gqlService, "%s", tc.literal, 1)) + if stmt.SupportsGraphQL != tc.want { + t.Errorf("SupportsGraphQL: %s parsed as %v, want %v", tc.literal, stmt.SupportsGraphQL, tc.want) + } + if !stmt.SupportsGraphQLSet { + t.Errorf("SupportsGraphQL: %s did not record that the author said anything", tc.literal) + } + if len(stmt.UnknownProperties) > 0 { + t.Errorf("SupportsGraphQL landed in UnknownProperties: %v", stmt.UnknownProperties) + } + } +} + +// TestSupportsGraphQLUnsetIsNotSet — an omitted value must leave Set false, so +// `create or modify` does not turn GraphQL OFF on a service that has it on +// merely by not mentioning it. There is no useful default to infer here, unlike +// PublishAssociations: false is what every service was before the property +// existed. +func TestSupportsGraphQLUnsetIsNotSet(t *testing.T) { + stmt := parseService(t, `create odata service M.Api ( + Path: 'odata/charts/', ServiceName: 'Api', Namespace: 'M.Charts', Version: '1.0.0' +) { publish entity M.Row as 'Rows' expose ( K (KEY) ) } +/`) + if stmt.SupportsGraphQLSet { + t.Error("an omitted SupportsGraphQL was recorded as set") + } + if stmt.SupportsGraphQL { + t.Error("an omitted SupportsGraphQL defaulted to true") + } +} + +// TestSupportsGraphQLIsAKnownProperty guards the surface that rejected it: +// unknown OData properties are an error (MDL-ODATA01), so a name missing from +// the list is not silently dropped — it fails the check. +func TestSupportsGraphQLIsAKnownProperty(t *testing.T) { + var found bool + for _, p := range knownODataServiceProps { + if p == "SupportsGraphQL" { + found = true + } + } + if !found { + t.Errorf("SupportsGraphQL missing from knownODataServiceProps: %v", knownODataServiceProps) + } +} diff --git a/mdl/executor/validate_odata_properties.go b/mdl/executor/validate_odata_properties.go index 9119ce961..ad31e060c 100644 --- a/mdl/executor/validate_odata_properties.go +++ b/mdl/executor/validate_odata_properties.go @@ -29,7 +29,7 @@ import ( var ( knownODataServiceProps = []string{ "Path", "Version", "ODataVersion", "Namespace", "ServiceName", - "Summary", "Description", "PublishAssociations", "Folder", + "Summary", "Description", "PublishAssociations", "SupportsGraphQL", "Folder", } knownPublishEntityProps = []string{ "ReadMode", "InsertMode", "UpdateMode", "DeleteMode", "UsePaging", "PageSize", diff --git a/mdl/visitor/visitor_odata.go b/mdl/visitor/visitor_odata.go index e13a88eb6..bbddd13ae 100644 --- a/mdl/visitor/visitor_odata.go +++ b/mdl/visitor/visitor_odata.go @@ -124,6 +124,9 @@ func (b *Builder) ExitCreateODataServiceStatement(ctx *parser.CreateODataService case "publishassociations": stmt.PublishAssociations = strings.EqualFold(value, "true") || strings.EqualFold(value, "yes") stmt.PublishAssociationsSet = true + case "supportsgraphql": + stmt.SupportsGraphQL = strings.EqualFold(value, "true") || strings.EqualFold(value, "yes") + stmt.SupportsGraphQLSet = true case "folder": stmt.Folder = value default: diff --git a/model/types.go b/model/types.go index 3f8c58a13..f58e5a770 100644 --- a/model/types.go +++ b/model/types.go @@ -498,17 +498,20 @@ func (s *ConsumedODataService) GetContainerID() ID { // PublishedODataService represents a published OData service. type PublishedODataService struct { BaseElement - ContainerID ID `json:"containerId"` - Name string `json:"name"` - Documentation string `json:"documentation,omitempty"` - Path string `json:"path,omitempty"` - Namespace string `json:"namespace,omitempty"` - ServiceName string `json:"serviceName,omitempty"` - Version string `json:"version,omitempty"` - ODataVersion string `json:"odataVersion,omitempty"` - Summary string `json:"summary,omitempty"` - Description string `json:"description,omitempty"` - PublishAssociations bool `json:"publishAssociations,omitempty"` + ContainerID ID `json:"containerId"` + Name string `json:"name"` + Documentation string `json:"documentation,omitempty"` + Path string `json:"path,omitempty"` + Namespace string `json:"namespace,omitempty"` + ServiceName string `json:"serviceName,omitempty"` + Version string `json:"version,omitempty"` + ODataVersion string `json:"odataVersion,omitempty"` + Summary string `json:"summary,omitempty"` + Description string `json:"description,omitempty"` + PublishAssociations bool `json:"publishAssociations,omitempty"` + // SupportsGraphQL publishes the same resources over GraphQL as well as + // OData. One boolean, one extra endpoint; the OData surface is unchanged. + SupportsGraphQL bool `json:"supportsGraphQL,omitempty"` UseGeneralization bool `json:"useGeneralization,omitempty"` AuthenticationTypes []string `json:"authenticationTypes,omitempty"` AuthMicroflow string `json:"authMicroflow,omitempty"` diff --git a/sdk/mpr/parser_odata.go b/sdk/mpr/parser_odata.go index 0697007fd..b4e8a5208 100644 --- a/sdk/mpr/parser_odata.go +++ b/sdk/mpr/parser_odata.go @@ -135,6 +135,7 @@ func (r *Reader) parsePublishedODataService(unitID, containerID string, contents svc.Summary = extractString(raw["Summary"]) svc.Description = extractString(raw["Description"]) svc.PublishAssociations = extractBool(raw["PublishAssociations"], false) + svc.SupportsGraphQL = extractBool(raw["SupportsGraphQL"], false) svc.UseGeneralization = extractBool(raw["UseGeneralization"], false) svc.Excluded = extractBool(raw["Excluded"], false) svc.AuthMicroflow = extractString(raw["AuthenticationMicroflow"]) diff --git a/sdk/mpr/writer_odata.go b/sdk/mpr/writer_odata.go index 6ee9e5e8e..96e6fe1d5 100644 --- a/sdk/mpr/writer_odata.go +++ b/sdk/mpr/writer_odata.go @@ -312,6 +312,7 @@ func (w *Writer) serializePublishedODataService(svc *model.PublishedODataService {Key: "Summary", Value: svc.Summary}, {Key: "Description", Value: svc.Description}, {Key: "PublishAssociations", Value: svc.PublishAssociations}, + {Key: "SupportsGraphQL", Value: svc.SupportsGraphQL}, {Key: "UseGeneralization", Value: svc.UseGeneralization}, {Key: "AuthenticationMicroflow", Value: svc.AuthMicroflow}, {Key: "AllowedModuleRoles", Value: allowedRoles}, diff --git a/sdk/versions/mendix-10.yaml b/sdk/versions/mendix-10.yaml index 285315f3c..7d70765db 100644 --- a/sdk/versions/mendix-10.yaml +++ b/sdk/versions/mendix-10.yaml @@ -145,6 +145,21 @@ features: mdl: "CREATE BUSINESS EVENT SERVICE Module.Name ..." odata_client: min_version: "10.0.0" + odata_graphql: + # A published OData service can also answer GraphQL — one boolean, same + # resources, an extra endpoint. Introduced in Studio Pro 10.14 as an + # EXPERIMENTAL feature: "Studio Pro now supports publishing GraphQL + # services. When you enable this setting, you can specify that a published + # OData service supports GraphQL, resulting in a service that supports both + # OData and GraphQL." (release notes, 10.14) + # + # The floor is documentary, not measured: the Mendix CDN no longer serves + # any 10.x mxbuild from here, so the earliest assembly available to check + # is 11.x. It matters because writing a property a version does not have + # is what makes Studio Pro refuse to open the document, so the gate is a + # real guard rather than a nicety. + min_version: "10.14.0" + mdl: "CREATE ODATA SERVICE Module.Name (SupportsGraphQL: Yes) ..." workflows: basic: diff --git a/sdk/versions/mendix-11.yaml b/sdk/versions/mendix-11.yaml index 7771e22e2..3a2a56a75 100644 --- a/sdk/versions/mendix-11.yaml +++ b/sdk/versions/mendix-11.yaml @@ -138,6 +138,21 @@ features: mdl: "CREATE BUSINESS EVENT SERVICE Module.Name ..." odata_client: min_version: "10.0.0" + odata_graphql: + # A published OData service can also answer GraphQL — one boolean, same + # resources, an extra endpoint. Introduced in Studio Pro 10.14 as an + # EXPERIMENTAL feature: "Studio Pro now supports publishing GraphQL + # services. When you enable this setting, you can specify that a published + # OData service supports GraphQL, resulting in a service that supports both + # OData and GraphQL." (release notes, 10.14) + # + # The floor is documentary, not measured: the Mendix CDN no longer serves + # any 10.x mxbuild from here, so the earliest assembly available to check + # is 11.x. It matters because writing a property a version does not have + # is what makes Studio Pro refuse to open the document, so the gate is a + # real guard rather than a nicety. + min_version: "10.14.0" + mdl: "CREATE ODATA SERVICE Module.Name (SupportsGraphQL: Yes) ..." agent_documents: # Agent Editor documents (Model, Knowledge Base, Consumed MCP Service, From 64135d453c59e277521d31de0a44c03fc46ece39 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 21:43:21 +0000 Subject: [PATCH 16/22] Refuse GraphQL with associations as an object id, and verify on a real view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found while verifying the grain-key guidance against an actual OQL view entity rather than a non-persistable stand-in. Flipping the same service to PublishAssociations: No produced two errors, not one: [CE7375] "Attribute ID for entity 'MyFirstModule.VMonthCategory' must be published and be the key when associations are exposed as an associated object id." [CE8055] "A service that supports GraphQL must publish associations as a link." CE7375 is the ledger's message reproduced verbatim, now on the entity kind they actually had. CE8055 is new: GraphQL has no representation for an associated object id, so the pair can never build whatever else the author does. That makes it a refusal rather than a warning — unlike PublishAssociations: No on its own, which stays a legitimate mode for a service whose key is arranged in Studio Pro. The view-entity verification, end to end on 11.13: OQL view over 4 source rows -> 3 grain rows (Rent 1200+300 = 1500) published keyed on (Period, Category), PublishAssociations Yes, SupportsGraphQL Yes -> mx check 0 errors GET /odata/charts/MonthCategories -> 200, the three aggregated rows POST /odata/charts/ {monthCategories{period category total}} -> 200, the same rows, camelCased GET /odata/charts/MonthCategories(Period='2026-07',Category='Rent') -> 200 {"Period":"2026-07","Category":"Rent","Total":1500.0} That last one is the point of the whole thread: the grain is a real key, so a client can re-read one row by it — which is what CE7375 was demanding an object id for, and what an object id would have made environment-specific. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4 --- .claude/skills/mendix/odata-data-sharing.md | 4 ++++ .../bug-tests/odata-graphql-service.mdl | 6 +++++ mdl/executor/cmd_odata.go | 13 ++++++++++ mdl/executor/cmd_odata_graphql_test.go | 24 +++++++++++++++++++ 4 files changed, 47 insertions(+) diff --git a/.claude/skills/mendix/odata-data-sharing.md b/.claude/skills/mendix/odata-data-sharing.md index e70e31c36..e8490da16 100644 --- a/.claude/skills/mendix/odata-data-sharing.md +++ b/.claude/skills/mendix/odata-data-sharing.md @@ -615,6 +615,10 @@ Three things that only bite once GraphQL is on: which OData accepts and GraphQL rejects. A service that built yesterday can fail on the day it is enabled. Give the set its own name: `publish entity Fin.VMonthCategory as 'MonthCategories'`. +- **`PublishAssociations` must be Yes.** GraphQL has no representation for an + associated object id, so Mendix refuses the pair: CE8055 "A service that + supports GraphQL must publish associations as a link." mxcli refuses it before + writing, since no other change can make it build. - **Mendix 10.14+**, where it arrived as an experimental feature. mxcli refuses the statement on an older project rather than writing a property that version's metamodel does not have — an unknown property is not a build error, it is a diff --git a/mdl-examples/bug-tests/odata-graphql-service.mdl b/mdl-examples/bug-tests/odata-graphql-service.mdl index 804ccba7f..d04b050b0 100644 --- a/mdl-examples/bug-tests/odata-graphql-service.mdl +++ b/mdl-examples/bug-tests/odata-graphql-service.mdl @@ -30,6 +30,12 @@ -- by more than just case." -- Hence `as 'MonthCategories'` below. The rule only appears once GraphQL is -- on, so a service that built yesterday can fail on the day it is enabled. +-- 3. PublishAssociations MUST be Yes. GraphQL has no representation for an +-- associated object id, so Mendix refuses the pair: +-- [error] [CE8055] "A service that supports GraphQL must publish +-- associations as a link." +-- mxcli refuses it before writing, since nothing else the author does can +-- make that combination build. create or replace non-persistent entity MyFirstModule.VMonthCategory ( Period: string(7), diff --git a/mdl/executor/cmd_odata.go b/mdl/executor/cmd_odata.go index bbbb91b16..3121705ac 100644 --- a/mdl/executor/cmd_odata.go +++ b/mdl/executor/cmd_odata.go @@ -1344,6 +1344,19 @@ func createODataService(ctx *ExecContext, stmt *ast.CreateODataServiceStmt) erro // does not have is not a build error, it is a document Studio Pro refuses to // open (InvalidOperationException at MprProperty.cs). Only checked when the // author asked for it, so nothing changes for the services that do not. + // GraphQL has no representation for an associated object id, so Mendix + // refuses the combination outright: CE8055 "A service that supports GraphQL + // must publish associations as a link." Refused here rather than warned + // about, because unlike PublishAssociations: No on its own — which is a + // legitimate mode for a service whose key is arranged in Studio Pro — this + // pair can never build, whatever else the author does. + if stmt.SupportsGraphQL && stmt.PublishAssociationsSet && !stmt.PublishAssociations { + return mdlerrors.NewValidation( + "SupportsGraphQL: Yes with PublishAssociations: No cannot build — a GraphQL service " + + "must publish associations as a link (CE8055). Remove PublishAssociations to take " + + "the default (Yes), or drop SupportsGraphQL.") + } + if stmt.SupportsGraphQL { if err := checkFeature(ctx, "integration", "odata_graphql", "SupportsGraphQL on a published OData service", diff --git a/mdl/executor/cmd_odata_graphql_test.go b/mdl/executor/cmd_odata_graphql_test.go index 9ec916b9b..0d561ae9b 100644 --- a/mdl/executor/cmd_odata_graphql_test.go +++ b/mdl/executor/cmd_odata_graphql_test.go @@ -96,3 +96,27 @@ func TestSupportsGraphQLIsAKnownProperty(t *testing.T) { t.Errorf("SupportsGraphQL missing from knownODataServiceProps: %v", knownODataServiceProps) } } + +// TestGraphQLRefusesObjectIdAssociations — Mendix rejects the pair outright: +// CE8055 "A service that supports GraphQL must publish associations as a link." +// GraphQL has no representation for an associated object id. +// +// Refused at execute rather than warned about, because unlike +// PublishAssociations: No on its own — a legitimate mode for a service whose +// key is arranged in Studio Pro — this combination can never build, whatever +// else the author does. Measured on 11.13 against a real OQL view entity: the +// same service is 0 errors with Yes, and CE7375 + CE8055 with No. +func TestGraphQLRefusesObjectIdAssociations(t *testing.T) { + stmt := parseService(t, `create odata service M.Api ( + Path: 'odata/charts/', ServiceName: 'Api', Namespace: 'M.Charts', + Version: '1.0.0', ODataVersion: OData4, + PublishAssociations: No, SupportsGraphQL: Yes +) { publish entity M.Row as 'Rows' expose ( K (KEY) ) } +/`) + if !stmt.SupportsGraphQL || stmt.PublishAssociations || !stmt.PublishAssociationsSet { + t.Fatalf("fixture did not parse as GraphQL+object-id: graphql=%v assoc=%v set=%v", + stmt.SupportsGraphQL, stmt.PublishAssociations, stmt.PublishAssociationsSet) + } + // The executor refuses this pair before writing; see createODataService. + // Pinned here so the fixture that triggers it cannot drift silently. +} From 6e7f0f291bf0fd905f44ecd763e073c0b85da104 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Aug 2026 05:32:03 +0000 Subject: [PATCH 17/22] Record what the GraphQL surface actually covers, and its two traps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The GraphQL checkbox does not say what it leaves out. Introspected and exercised on 11.13, on one resource published over both surfaces at once: $select -> inherent; naming the fields IS the projection $top -> first: Int $skip -> offset: Int $orderby -> orderBy: [{field: ASC|DESC}] key lookup-> a singular field, vMonthCategory(period:, category:) $filter -> ABSENT $count -> ABSENT The whole schema for a one-entity service is nine types — Query, SortOrder, the entity, its order input and the scalars. No filter type, no where type, no count type exists in it. ($expand is untested; the probe has no associations.) Two traps, both measured rather than inferred: - orderBy must be a LIST. `orderBy: {total: DESC}` fails with "Incorrect value for orderBy" while `[{total: DESC}]` works, and introspection advertises the argument as a bare input object rather than a list. The schema and the parser disagree, and the error does not say which way. - An unknown argument is SILENTLY IGNORED. Both `where: {…}` and `bogusArgument: 42` return 200 with the full result set. A client that assumes a filter argument exists gets every row and no warning — the same "200 with the wrong rows" failure the pushdown work is about, surfacing somewhere new. So paging and sorting are safe over GraphQL and filtering is not there. A widget needing server-side filtering has to use OData, which is a reason to keep the OData surface even on a service with GraphQL enabled. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4 --- .claude/skills/mendix/odata-data-sharing.md | 39 +++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/.claude/skills/mendix/odata-data-sharing.md b/.claude/skills/mendix/odata-data-sharing.md index e8490da16..ada55d2fd 100644 --- a/.claude/skills/mendix/odata-data-sharing.md +++ b/.claude/skills/mendix/odata-data-sharing.md @@ -624,6 +624,45 @@ Three things that only bite once GraphQL is on: metamodel does not have — an unknown property is not a build error, it is a document Studio Pro will not open. +### What GraphQL actually covers, measured + +The GraphQL surface is narrower than the OData one, and the gaps are not +documented next to the checkbox. Introspected and exercised on 11.13, on a +resource published over both at once: + +| OData | GraphQL | +|---|---| +| `$select` | **inherent** — you name the fields, that *is* the projection | +| `$top` | `first: Int` | +| `$skip` | `offset: Int` | +| `$orderby` | `orderBy: [{field: ASC\|DESC}]` | +| key lookup `Set(K='v')` | a singular field: `vMonthCategory(period: "…", category: "…")` | +| **`$filter`** | **absent** | +| **`$count`** | **absent** | +| `$expand` | not measured here (the probe has no associations) | + +The whole schema for a one-entity service is nine types — `Query`, +`SortOrder`, the entity, its order input, and the scalars. There is no filter +type, no where type and no count type in it. + +Two traps, both measured: + +- **`orderBy` must be a LIST.** `orderBy: {total: DESC}` fails with + `Incorrect value for orderBy`, while `orderBy: [{total: DESC}]` works — and + introspection advertises the argument as a bare input object + (`VMonthCategoryOrderInput`), not a list, so the schema and the parser + disagree. The error does not mention it. +- **An unknown argument is silently ignored.** `monthCategories(where: {…})` + and even `monthCategories(bogusArgument: 42)` both return **200 with the + full result set** rather than an error. A client that assumes a filter + argument exists gets every row and no warning — the same "200 with the wrong + rows" failure the pushdown pack was written about, in a different surface. + +So: **paging and sorting are safe over GraphQL; filtering is not there.** A +widget that needs server-side filtering has to use the OData surface, and a +resource where the client filters is a reason to keep OData even when GraphQL +is enabled. + GraphQL here is not as complete as the OData surface — it is a second way to read the same published resources, which some widgets and clients prefer. From 6fdfd4fcee478cdcd4494c357fceaf6f246098a3 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Aug 2026 05:18:33 +0000 Subject: [PATCH 18/22] CE6624 does not apply to a view entity, so a grain is not always needed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A Studio Pro service publishing a view entity keyed on a SINGLE column, with no unique validation rule, reports 0 errors. The guidance shipped here implied otherwise: its table had CE6624 for a single-attribute key and offered the grain as the way out, which is right for a persistable entity and wrong for a view. Reproduced rather than taken on faith — same probe, view entity, one KEY attribute, no validation rule: 0 errors on 11.13. So the rule is narrower than stated. CE6624 is a persistable-entity requirement; a view cannot carry a validation rule at all (CE0070) and is not asked for one. If a view already has a naturally unique column — an id carried through from the source data, not the platform's object id — key on that. The grain is for when no single column identifies a row, which is the normal case for an aggregate but not for a flattening view. The business-key argument is unaffected: the column to key on is one the domain guarantees, whether it arrives as one column or as the grain. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4 --- .claude/skills/mendix/odata-data-sharing.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.claude/skills/mendix/odata-data-sharing.md b/.claude/skills/mendix/odata-data-sharing.md index 40fc882b3..393acaff0 100644 --- a/.claude/skills/mendix/odata-data-sharing.md +++ b/.claude/skills/mendix/odata-data-sharing.md @@ -533,6 +533,7 @@ Measured on Mendix 11.13, each row a separate build: |---|---| | single key attribute, persistable, no `unique` rule | **CE6624** — add one | | single key attribute, persistable, `unique error '…'` | 0 errors | +| **single key attribute, VIEW entity, no `unique` rule** | **0 errors** | | **composite key, `OData3`** | **CE7238** "You can only have more than one key attribute when the OData version is 4" | | composite key, `OData4`, persistable, no `unique` rules | 0 errors | | **composite key, `OData4`, non-persistable, no `unique` rules** | **0 errors** | @@ -547,6 +548,13 @@ Two consequences worth holding on to: *single*-attribute key, where one attribute has to be unique by itself — which is exactly the case a grain is not. So the CE6624 hurdle disappears the moment the key is honest about being multi-column. +- **CE6624 does not apply to a view entity at all.** A view can carry a + *single*-attribute key with no validation rule and build cleanly — confirmed + against a Studio Pro service publishing a view keyed on one column. So if the + view already has a naturally unique column (an id carried through from the + source data, not the platform's object id), key on that and skip the grain. + Reach for the grain when no single column identifies a row — which is the + normal case for an aggregate. ```sql create non-persistent entity Fin.VMonthCategory ( From ca6f7f881d25de1156cf1361db8da83a80eeffca Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Aug 2026 05:23:06 +0000 Subject: [PATCH 19/22] A view entity read from the database already pushes down the query options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The published-entity dialog shows Action: "Read from database" with Countable, Top supported and Skip supported all ticked, on a view entity. That is worth measuring, because it decides whether a project needs any pushdown machinery at all. Measured on 11.13 against a running app — an OQL view over four rows aggregating to three, published with the database read and no Java anywhere: $top=1 -> 1 row, not 3 $count=true&$top=1 -> "@odata.count": 3, one row returned $filter=Category eq 'Rent' -> only the Rent row $orderby=Total desc&$skip=1 -> [400, 250] (1500 correctly skipped) So aggregation happens in the database and paging and filtering push down to it. A chart or grid can page a large resource with nothing hand-written — which is the capability mendix-odata-pushdown exists to recreate. That pack is for the case a view cannot cover: a resource with no table behind it, where a read microflow is the only way to produce the rows and Mendix applies none of the query options to them. Recorded as the contrast, so the pack is reached for when it is needed rather than by default. The same dialog also states the grain rule in Mendix's own words: "Choose the attribute(s) that form the key of this entity. These attributes should never be empty, and should together form a unique identifier." Multi-column keys, uniqueness on the combination, and required — which is what the grain guidance already says. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4 --- .claude/skills/mendix/odata-data-sharing.md | 35 +++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.claude/skills/mendix/odata-data-sharing.md b/.claude/skills/mendix/odata-data-sharing.md index 393acaff0..9dfb54fc6 100644 --- a/.claude/skills/mendix/odata-data-sharing.md +++ b/.claude/skills/mendix/odata-data-sharing.md @@ -511,6 +511,41 @@ That is also why the key needs `unique error '…'` on the attribute — see the CE6624 note below. Both halves of the same idea: the value identifies one row, and keeps identifying it. +### A view entity read from the database gets the query options for free + +**This decides whether you need any pushdown machinery at all**, so check it +before reaching for Java. + +A published resource has an `Action`: *Read from database*, or a read microflow. +The difference is not a detail: + +| Action | `$filter` `$orderby` `$top` `$skip` `$count` | +|---|---| +| **Read from database** (a view entity, or a persistable one) | **Mendix applies them** — they reach the database | +| Read microflow | Mendix applies **none** of them; whatever the microflow returns is what the client gets | + +Measured on 11.13 against a running app — an OQL view over four rows aggregating +to three, published with `Action: Read from database` and no Java anywhere: + +``` +$top=1 -> 1 row, not 3 +$count=true&$top=1 -> "@odata.count": 3, one row returned +$filter=Category eq 'Rent' -> only the Rent row +$orderby=Total desc&$skip=1 -> [400, 250] (1500 correctly skipped) +``` + +So for a **view entity**, aggregation happens in the database and paging and +filtering push down to it — a chart or grid can page a large resource with +nothing hand-written. That is the whole capability the `mendix-odata-pushdown` +pack exists to recreate. + +The pack is for the case a view cannot cover: a resource with **no table +behind it** — a warehouse view over another system, a stored procedure, a CSV +through a connector — where a read microflow is the only way to produce the +rows, and Mendix then applies nothing to them (a `?$top=5` that quietly returns +all 917 rows). If a view entity can express the resource, prefer it and skip +the machinery. + ### An aggregate view's key is its grain A summary resource — an OQL view entity, or a non-persistable row filled by a From a98c52351bd2ba7a37774af8f110cdcf2793b8ae Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Aug 2026 05:25:04 +0000 Subject: [PATCH 20/22] Describe the topology the pushdown pack is actually for The previous wording listed "a CSV through a connector" among the cases a view entity cannot cover, which is true and flattens the point. The shape that makes the pushdown load-bearing is two apps: frontend app --- external entities / OData ---> backend app (grid, chart) (no data of its own) | external database connector | DuckDB over CSV The frontend pages and filters by generating $top/$skip/$filter, because external entities ARE OData and it has no other vocabulary. The backend's read microflow has to translate those into the SQL it sends through the connector. Without that translation the paging still looks correct while every page drags the whole file across, and neither app reports anything. Two things follow that the old phrasing obscured: - "Prefer a view entity" is not advice that applies here. The data is not in this app's database, so there is no table to select from. The real question is only whether THIS app owns the data. - The consumer's capability flags have to match the service: an external entity generated with TopSupported/SkipSupported the service does not honour is CE6630 in the consuming app. The two ends are checked against each other. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4 --- .claude/skills/mendix/odata-data-sharing.md | 41 ++++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/.claude/skills/mendix/odata-data-sharing.md b/.claude/skills/mendix/odata-data-sharing.md index 9dfb54fc6..48a705c11 100644 --- a/.claude/skills/mendix/odata-data-sharing.md +++ b/.claude/skills/mendix/odata-data-sharing.md @@ -539,12 +539,41 @@ filtering push down to it — a chart or grid can page a large resource with nothing hand-written. That is the whole capability the `mendix-odata-pushdown` pack exists to recreate. -The pack is for the case a view cannot cover: a resource with **no table -behind it** — a warehouse view over another system, a stored procedure, a CSV -through a connector — where a read microflow is the only way to produce the -rows, and Mendix then applies nothing to them (a `?$top=5` that quietly returns -all 917 rows). If a view entity can express the resource, prefer it and skip -the machinery. +The pack is for the case a view cannot cover: **the data is not in this app's +database at all**, so there is no table for a view to select from and a read +microflow is the only way to produce the rows. Mendix then applies nothing to +them — a `?$top=5` that quietly returns all 917 rows. + +Its motivating shape is two apps, and the topology is what makes the pushdown +load-bearing rather than an optimisation: + +``` +frontend app --- external entities / OData ---> backend app +(grid, chart) (no data of its own) + | + external database connector + | + DuckDB over CSV +``` + +The frontend's grid pages and filters by generating `$top` / `$skip` / +`$filter` — it has no other vocabulary, because external entities *are* OData. +The backend's read microflow has to translate those options into the SQL it +sends through the connector. Without that translation the frontend's paging +still looks correct while every page drags the whole file across, and nothing +in either app reports a problem. + +Two consequences worth holding on to: + +- **A view entity is not an option here**, so "prefer the view" is not advice + that applies. The question is only whether *this app* owns the data. +- **The consumer's capability flags must match the service.** An external + entity generated with `TopSupported`/`SkipSupported` that the service does + not honour is CE6630 in the consuming app — the two ends of this contract + are checked against each other. + +If the resource *is* backed by this app's own tables, prefer a view entity and +skip the machinery entirely. ### An aggregate view's key is its grain From 1952608204555fd8cbdbc98e50700a2b0095e114 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Aug 2026 05:42:57 +0000 Subject: [PATCH 21/22] Measure which query options a read microflow must implement itself MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Mendix applies none of the query options to a read-microflow resource" is the received wisdom and it is not right. Same view served both ways on 11.13, three rows behind each: option database read read microflow $select applied APPLIED - Mendix projects the response either way $filter applied 200, unfiltered $orderby applied 200, unsorted $top/$skip applied 200, full set $count applied needs System.ODataResponse (CE6962) Two consequences the all-or-nothing version obscures: - $select is not the microflow's correctness problem. The client already receives only the fields it asked for, and the CONSUMER drives it: removing attributes from an external entity narrows the $select it sends, because the external entity has nowhere to put what it dropped. So pushing $select into the source query is a cost optimisation — fewer columns read at the source — never a fix for wrong output. Worth knowing before writing code for it. - Declaring the capability is what turns a safe refusal into a silent lie. Without Filterable/Sortable, Mendix rejects the request outright: 400 "Property 'Category' is non-filterable." Declare them — which you must, or no client can filter at all — and the identical request answers 200 with every row. That second one is the sharpest statement of why the pushdown work exists: the failure is created BY promising the capability, and the microflow is the only place left to keep the promise. Three commits recovered here rather than stacked on merged history: they were pushed to the #157 branch after that PR had already been merged, so they never reached main. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4 --- .claude/skills/mendix/odata-data-sharing.md | 31 +++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.claude/skills/mendix/odata-data-sharing.md b/.claude/skills/mendix/odata-data-sharing.md index 48a705c11..0cbcf932a 100644 --- a/.claude/skills/mendix/odata-data-sharing.md +++ b/.claude/skills/mendix/odata-data-sharing.md @@ -539,6 +539,37 @@ filtering push down to it — a chart or grid can page a large resource with nothing hand-written. That is the whole capability the `mendix-odata-pushdown` pack exists to recreate. +**Which options a read microflow actually has to implement — measured, and not +all-or-nothing.** The same view served both ways on 11.13, three rows behind +each: + +| option | database read | read microflow | +|---|---|---| +| `$select` | applied | **applied** — Mendix projects the response either way | +| `$filter` | applied | **200, unfiltered** | +| `$orderby` | applied | **200, unsorted** | +| `$top` / `$skip` | applied | **200, full set** | +| `$count` | applied | needs `System.ODataResponse` (CE6962) | + +Two things follow that "Mendix applies none of them" gets wrong: + +- **`$select` is not the microflow's correctness problem.** The client already + receives only the fields it asked for. And the consumer drives it: removing + attributes from an external entity narrows the `$select` it sends, because the + external entity has nowhere to put what it dropped. So pushing `$select` into + the source query is a *cost* optimisation — fewer columns read at the source — + never a fix for wrong output. +- **Declaring the capability is what turns a safe refusal into a silent lie.** + With `Filterable`/`Sortable` *not* declared, Mendix rejects the request: + `400 "Property 'Category' is non-filterable."` Declare them — which you must, + or no client can filter at all — and the identical request becomes 200 with + every row. The declaration is a promise Mendix enforces at the boundary and + does not keep for you. + +That second one is the sharpest statement of why this work exists: the failure +is *created by* promising the capability, and the microflow is the only place +left to keep the promise. + The pack is for the case a view cannot cover: **the data is not in this app's database at all**, so there is no table for a view to select from and a read microflow is the only way to produce the rows. Mendix then applies nothing to From 26e379bbcf2669a34d2518738fd29ae036c4608d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Aug 2026 05:59:14 +0000 Subject: [PATCH 22/22] Push $select into the source query, and give the pack a runnable verify MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit $select is the one OData option worth adding here and the one whose value is easy to overstate, so the code says which it is. Mendix applies $select to the response itself — measured on 11.13, on a microflow-backed resource as much as on a database read — so the client already receives only the fields it asked for whatever the microflow does. Pushing it down saves READING columns nobody looks at: real time over a wide CSV through a columnar reader, nothing over a narrow table. The consumer drives it, since an external entity with attributes removed sends a narrower $select — it has nowhere to put what it dropped. Result gains selectSql (" a, b, c", spliced after SELECT) and selectedColumns (the same as exposed names, for binding callers), carried onto the Query entity as SelectSql / SelectedColumns. Three decisions, none of them obvious: - An unknown column is REJECTED, not skipped. sortTerms ignores what it cannot place because a wrong order is cosmetic; a dropped projection is not — answering with a null where data was expected is the same "200 and wrong" this component exists to prevent. - The key is always projected, even when $select omits it. One column, and it stops a caller that dedupes, associates or re-reads by key from losing the value it does that with. The client still sees only what it asked for. - Sort columns are NOT forced in. ORDER BY may name a column the SELECT list omits, which is ordinary SQL, and adding them would defeat the narrowing. $expand stays unsupported and is rejected rather than ignored. It is a different kind of work — not a projection but a join producing a nested object graph the microflow would have to build as associated objects, with nested options multiplying the surface. Written down next to $search, $apply and the lambda operators so the boundary is a decision rather than an omission. Also fixes a bug the module rewrite exists to prevent and did not catch: QueryObject declared ENTITY = "ODataPushdown.Query" as a literal, in a file whose package line is tokenised. Installing with --module Warehouse gave `package warehouse;` alongside an instantiate of ODataPushdown.Query — a class that compiles and finds nothing. Now {{MODULE}}.Query, verified by installing under a different module name. scripts/ParserCheck.java is the verify: the proposal asks for and this pack wanted most. The parser takes no Mendix types, so a dialect or grammar regression is checkable in a second where every other test of it needs an app, a database and a request. It covers the projection, the filter grammar's unquoting of numeric columns (the combo-box-vs-grid-header case), the sort terms, the MaxTop clamp, $count, and that an unreadable filter is rejected. Confirmed to fail: disabling the key-inclusion rule exits 1 and prints the diff. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4 --- .../packs/mendix-odata-pushdown/SKILL.md | 65 +++++++++++++ .../java/ODataQueryParser.java | 91 +++++++++++++++++ .../java/QueryObject.java | 4 +- .../mendix-odata-pushdown/mdl/module.mdl | 14 +++ .../packs/mendix-odata-pushdown/pack.yaml | 6 ++ .../scripts/ParserCheck.java | 97 +++++++++++++++++++ 6 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 .claude/skills/packs/mendix-odata-pushdown/scripts/ParserCheck.java diff --git a/.claude/skills/packs/mendix-odata-pushdown/SKILL.md b/.claude/skills/packs/mendix-odata-pushdown/SKILL.md index 8cd7f8ac6..b98404a9a 100644 --- a/.claude/skills/packs/mendix-odata-pushdown/SKILL.md +++ b/.claude/skills/packs/mendix-odata-pushdown/SKILL.md @@ -49,6 +49,8 @@ builds the invocation for a resource backed by a stored routine. |---|---|---| | `FilterSql` | splice | `" WHERE …"`, or empty | | `OrderBySql` | splice | `" ORDER BY … LIMIT n OFFSET m"` | +| `SelectSql` | splice | `" a, b, c"` for `$select`, or empty for all columns | +| `SelectedColumns` | bind | the same columns as exposed names | | `Key` | bind | the key the client is re-reading one row by; empty for a collection | | `Top`, `Skip` | bind | the page, already clamped to `MaxTop` | | `SortColumn1/2`, `SortDirection1/2` | bind | the sort, as exposed names and `A`/`D` | @@ -140,3 +142,66 @@ and four Java actions. Without it the pack only copies its own files. | `references/patterns.md` | splice and bind end to end, and the sortable-fixed-statement `CASE` | | `references/failure-modes.md` | what breaks, symptom first | | `references/packaging-gap.md` | why this pack does not install, and how to apply it by hand | + +## `$select` narrows the read, it does not fix the answer + +`SelectSql` is the one option here that is an optimisation rather than a +correction, and it is worth knowing which before spending time on it. + +Mendix applies `$select` to the response **itself** — measured on 11.13, on a +microflow-backed resource as much as on a database read — so the client already +receives only the fields it asked for whatever the microflow does. What pushing +it down saves is *reading* columns nobody will look at: real time over a wide +CSV through a columnar reader, nothing at all over a narrow table. + +The consumer drives it. An external entity with attributes removed sends a +narrower `$select`, because it has nowhere to put what it dropped — so the +projection is negotiated end to end without either side arranging it. + +Splice it after `SELECT`, and keep your own list when it is empty: + +```sql +SELECT {{SelectSql or your full list}} FROM read_csv_auto(?) AS t +{{FilterSql}}{{OrderBySql}} +``` + +Three behaviours worth relying on: + +- **The key is always projected**, even when `$select` omits it. It costs one + column and stops a caller that dedupes, associates or re-reads by key from + losing the value it does that with. The client still sees only what it asked + for, because Mendix projects the response. +- **An unknown column is rejected, not skipped.** A wrong sort order is + cosmetic and is ignored; a dropped projection is not — answering with a null + where data was expected is the same "200 and wrong" this component exists to + prevent. +- **Sort columns are not forced into the projection.** `ORDER BY` may name a + column the `SELECT` list omits; that is ordinary SQL, and adding them would + defeat the narrowing. + +### What is still not translated + +`$expand` is **not** supported, and is rejected rather than ignored when +`RejectUnsupported` is on. It is a different kind of work from everything else +here: not a projection but a join producing a nested object graph, which the +microflow would have to build as associated Mendix objects, with nested options +(`$expand=X($filter=…;$top=3)`) multiplying the surface. `$search`, `$apply` and +the lambda operators (`any`/`all`) are unhandled for the same reason — each is a +new grammar rather than a new clause. + +## Checking it without an app + +```bash +mkdir -p /tmp/pc/odatapushdown +sed -e 's/{{MODULE_PATH}}/odatapushdown/g' -e 's/{{MODULE}}/ODataPushdown/g' \ + java/ODataQueryParser.java > /tmp/pc/odatapushdown/ODataQueryParser.java +javac -d /tmp/pc /tmp/pc/odatapushdown/ODataQueryParser.java scripts/ParserCheck.java +java -cp /tmp/pc ParserCheck +``` + +`ParserCheck` exits non-zero on the first failure and prints what it expected. +It covers the projection, the filter grammar's quoting of numeric columns, the +sort terms, the `MaxTop` clamp, `$count`, and that an unreadable filter is +rejected rather than dropped. A dialect regression here is invisible to +`mx check` and to every test that needs an app, which is why it is worth a +second. diff --git a/.claude/skills/packs/mendix-odata-pushdown/java/ODataQueryParser.java b/.claude/skills/packs/mendix-odata-pushdown/java/ODataQueryParser.java index 795991d0e..5d217caba 100644 --- a/.claude/skills/packs/mendix-odata-pushdown/java/ODataQueryParser.java +++ b/.claude/skills/packs/mendix-odata-pushdown/java/ODataQueryParser.java @@ -76,6 +76,13 @@ public static final class Result { public String sortDirection1 = "A"; public String sortColumn2 = ""; public String sortDirection2 = "A"; + /** + * " a, b, c" — the columns to project, or "" when the client asked for + * everything and the caller should use its own full list. + */ + public String selectSql = ""; + /** The exposed names projected, comma-separated, for callers that bind. */ + public String selectedColumns = ""; /** True when the request asked for something untranslatable. */ public boolean rejected; public String rejectReason = ""; @@ -155,6 +162,13 @@ public static Result parse(String uri, String columnMap, String dialect, r.sortDirection2 = terms.get(1)[1]; } r.orderBySql = orderBySql(terms, cols, defaultOrderBy, d, r.top, r.skip); + + try { + selectInto(r, trim(opts.get("$select")), cols, keyField); + } catch (IllegalArgumentException e) { + r.rejected = true; + r.rejectReason = e.getMessage(); + } return r; } @@ -397,6 +411,83 @@ private static boolean safeKey(String v) { // ---------------------------------------------------------------- order + /** + * Narrows the projection to what {@code $select} asked for. + * + *

Unlike the other options this one is not a correctness fix. + * Mendix applies {@code $select} to the response itself, on a + * microflow-backed resource as much as on a database read — measured on + * 11.13 — so the client already receives only the fields it asked for + * whatever this does. What it saves is reading columns nobody will look at, + * which is worth real time when the source is a columnar reader over a wide + * CSV and worth nothing when it is a narrow table. The consumer drives it: + * an external entity with attributes removed sends a narrower + * {@code $select}, because it has nowhere to put what it dropped. + * + *

Three decisions, none of them obvious: + * + *

    + *
  • An unknown name is rejected, not skipped. A wrong sort order is + * cosmetic and {@link #sortTerms} ignores what it cannot place; a + * dropped projection is not. Silently omitting a column the client + * asked for answers with a null where data was expected, which is the + * same "200 and wrong" this component exists to prevent. Mendix + * normally rejects an unpublished name before the microflow is reached, + * so this is the belt to that braces. + *
  • The key is always projected. It costs one column and it stops + * a caller that dedupes, associates or re-reads by key from silently + * losing the value it does that with. A client asking for one field + * still gets one field: Mendix projects the response. + *
  • Sort columns are not forced in. {@code ORDER BY} may name a + * column the SELECT list omits — that is ordinary SQL — and adding them + * would defeat the point of narrowing. It matters only if a caller + * wraps this in a subquery or a DISTINCT, which the splice form does + * not. + *
+ */ + private static void selectInto(Result r, String select, Map cols, + String keyField) { + if (select.isEmpty() || "*".equals(select)) { + return; // the client wants everything; the caller keeps its own list + } + Map chosen = new LinkedHashMap<>(); // exposed -> sql + for (String raw : select.split(",")) { + String name = raw.trim(); + if (name.isEmpty()) { + continue; + } + Column c = cols.get(name.toLowerCase()); + if (c == null) { + throw new IllegalArgumentException("$select names " + name + + ", which is not a column of this resource"); + } + chosen.put(name, c.sql); + } + if (chosen.isEmpty()) { + return; + } + String key = trim(keyField); + if (!key.isEmpty() && !chosen.containsKey(key)) { + Column kc = cols.get(key.toLowerCase()); + if (kc != null) { + chosen.put(key, kc.sql); + } + } + + StringBuilder sql = new StringBuilder(); + StringBuilder names = new StringBuilder(); + for (Map.Entry e : chosen.entrySet()) { + if (sql.length() > 0) { + sql.append(","); + names.append(","); + } + sql.append(" ").append(e.getValue()); + names.append(e.getKey()); + } + r.selectSql = sql.toString(); + r.selectedColumns = names.toString(); + } + private static List sortTerms(String orderby, Map cols) { List out = new ArrayList<>(); if (orderby.isEmpty()) { diff --git a/.claude/skills/packs/mendix-odata-pushdown/java/QueryObject.java b/.claude/skills/packs/mendix-odata-pushdown/java/QueryObject.java index b46dd91aa..a26fa7d26 100644 --- a/.claude/skills/packs/mendix-odata-pushdown/java/QueryObject.java +++ b/.claude/skills/packs/mendix-odata-pushdown/java/QueryObject.java @@ -16,7 +16,7 @@ public final class QueryObject { /** The entity this component publishes its answer as. */ - public static final String ENTITY = "ODataPushdown.Query"; + public static final String ENTITY = "{{MODULE}}.Query"; private QueryObject() { } @@ -53,6 +53,8 @@ public static IMendixObject parse(IContext context, String uri, String columns, o.setValue(context, "Top", r.top); o.setValue(context, "Skip", r.skip); o.setValue(context, "WantsCount", r.wantsCount); + o.setValue(context, "SelectSql", r.selectSql); + o.setValue(context, "SelectedColumns", r.selectedColumns); o.setValue(context, "SortColumn1", r.sortColumn1); o.setValue(context, "SortDirection1", r.sortDirection1); o.setValue(context, "SortColumn2", r.sortColumn2); diff --git a/.claude/skills/packs/mendix-odata-pushdown/mdl/module.mdl b/.claude/skills/packs/mendix-odata-pushdown/mdl/module.mdl index 5602d0aaf..717aa7adf 100644 --- a/.claude/skills/packs/mendix-odata-pushdown/mdl/module.mdl +++ b/.claude/skills/packs/mendix-odata-pushdown/mdl/module.mdl @@ -97,6 +97,20 @@ create or modify non-persistent entity {{MODULE}}.Query ( FilterSql: String(4000), /** " ORDER BY … LIMIT n OFFSET m", for callers that build their own SQL. */ OrderBySql: String(2000), + /** + * " a, b, c" — the columns $select asked for, or empty for all of them. + * + * Splice it after SELECT. Empty means the client wants everything, so keep + * your own list rather than emitting nothing. + * + * This one saves work, it does not fix output: Mendix applies $select to the + * response itself even on a microflow-backed resource, so the client already + * sees only what it asked for. Narrowing the source query is worth real time + * over a wide CSV and nothing over a narrow table. + */ + SelectSql: String(4000), + /** The same columns as exposed names, for callers that bind. */ + SelectedColumns: String(2000), /** The key the client is re-reading one row by; empty for a collection. */ Key: String(200), /** Page size, already clamped to the resource's MaxTop. */ diff --git a/.claude/skills/packs/mendix-odata-pushdown/pack.yaml b/.claude/skills/packs/mendix-odata-pushdown/pack.yaml index 446ac7602..200fd9307 100644 --- a/.claude/skills/packs/mendix-odata-pushdown/pack.yaml +++ b/.claude/skills/packs/mendix-odata-pushdown/pack.yaml @@ -65,4 +65,10 @@ installs: java: - java +# A self-check that needs no Mendix runtime. ODataQueryParser takes no Mendix +# types in its signature, so a dialect or grammar regression is checkable in a +# second — where every other test of this component needs an app, a database +# and a request. Substitute the tokens, javac, run; non-zero on failure. +verify: scripts/ParserCheck.java + source: https://github.com/ako/mxcli-formula1/tree/main/model/odatapushdown diff --git a/.claude/skills/packs/mendix-odata-pushdown/scripts/ParserCheck.java b/.claude/skills/packs/mendix-odata-pushdown/scripts/ParserCheck.java new file mode 100644 index 000000000..694aecadb --- /dev/null +++ b/.claude/skills/packs/mendix-odata-pushdown/scripts/ParserCheck.java @@ -0,0 +1,97 @@ +// A self-check for ODataQueryParser that needs no Mendix runtime. +// +// The parser takes no Mendix types in its signature — strings in, strings out — +// which is the whole reason it is a separate class from QueryObject. That makes +// a dialect or grammar regression checkable in about a second, where every other +// test of this component needs an app, a database and a request. +// +// Run it from the pack directory after substitution: +// +// mkdir -p /tmp/pc && sed -e 's/{{MODULE_PATH}}/odatapushdown/g' \ +// -e 's/{{MODULE}}/ODataPushdown/g' java/ODataQueryParser.java \ +// > /tmp/pc/ODataQueryParser.java +// javac -d /tmp/pc /tmp/pc/ODataQueryParser.java scripts/ParserCheck.java +// java -cp /tmp/pc ParserCheck +// +// Exits non-zero on the first failure, and prints what it expected. +import odatapushdown.ODataQueryParser; + +public class ParserCheck { + + private static final String COLS = + "period:t.period:text,category:t.category:text,total:t.total:number"; + private static int failures = 0; + + public static void main(String[] args) { + // $select — the projection. Not a correctness fix (Mendix projects the + // response itself); this narrows what the SOURCE reads. + eq("no $select leaves the caller's own list alone", sel("/Rows"), ""); + eq("a single column, plus the key", + sel("/Rows?$select=category"), " t.category, t.period"); + eq("order follows the request, key appended", + sel("/Rows?$select=category,total"), " t.category, t.total, t.period"); + eq("the key is not duplicated when already asked for", + sel("/Rows?$select=period"), " t.period"); + eq("$select=* is everything", sel("/Rows?$select=*"), ""); + eq("exposed names come back for binding callers", + parse("/Rows?$select=total").selectedColumns, "total,period"); + + // An unknown column is REJECTED, not skipped: omitting a field the + // client asked for answers with a null where data was expected. + yes("an unknown $select column is rejected", parse("/Rows?$select=nope").rejected); + no("a known one is not", parse("/Rows?$select=total").rejected); + + // The options that were already here, so this is a check of the + // component and not only of the newest part of it. + yes("$filter reaches SQL", parse("/Rows?$filter=category eq 'Rent'") + .filterSql.contains("t.category")); + yes("a numeric column is compared unquoted", + parse("/Rows?$filter=total eq 1500").filterSql.contains("1500") + && !parse("/Rows?$filter=total eq 1500").filterSql.contains("'1500'")); + yes("a quoted literal on a numeric column is unquoted too — a combo box " + + "sends 'x' where a grid header sends x", + !parse("/Rows?$filter=total eq '1500'").filterSql.contains("'1500'")); + eq("$orderby is read as an exposed name", + parse("/Rows?$orderby=total desc").sortColumn1, "total"); + eq("...with its direction", parse("/Rows?$orderby=total desc").sortDirection1, "D"); + yes("$top clamps to maxTop", parse("/Rows?$top=99999").top <= 500); + yes("$count is seen", parse("/Rows?$count=true").wantsCount); + yes("an unreadable $filter is rejected rather than dropped", + parse("/Rows?$filter=category eq").rejected); + + if (failures > 0) { + System.out.println(failures + " check(s) failed"); + System.exit(1); + } + System.out.println("ParserCheck: all checks passed"); + } + + private static ODataQueryParser.Result parse(String uri) { + return ODataQueryParser.parse(uri, COLS, "duckdb", 500, 100, "", "period"); + } + + private static String sel(String uri) { + return parse(uri).selectSql; + } + + private static void eq(String what, String got, String want) { + if (!want.equals(got)) { + System.out.println("FAIL " + what + "\n got '" + got + "'\n want '" + want + "'"); + failures++; + } + } + + private static void yes(String what, boolean got) { + if (!got) { + System.out.println("FAIL " + what); + failures++; + } + } + + private static void no(String what, boolean got) { + if (got) { + System.out.println("FAIL " + what + " (expected false)"); + failures++; + } + } +}